zen-fs-config 0.5.32 → 0.5.34
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 +211 -11
- package/dist/index.d.ts +211 -11
- package/dist/index.js +320 -4
- package/dist/index.mjs +316 -4
- package/dist/zen-fs-config.js +328 -16
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -25,7 +25,7 @@ import { SyncMapStore, SyncMapTransaction } from '@zenfs/core';
|
|
|
25
25
|
* deleting them would cost network calls and lose mtime precision.
|
|
26
26
|
*/
|
|
27
27
|
/** Minimal async FS surface needed for the purge. */
|
|
28
|
-
interface PurgeableFS {
|
|
28
|
+
interface PurgeableFS$1 {
|
|
29
29
|
readdir(path: string): Promise<string[]>;
|
|
30
30
|
stat(path: string): Promise<{
|
|
31
31
|
mode?: number;
|
|
@@ -57,11 +57,78 @@ declare function isMtimeSidecar(fileName: string): boolean;
|
|
|
57
57
|
* Delete every `.mtime` sidecar stored in a local primary backend.
|
|
58
58
|
*
|
|
59
59
|
* Walks the whole tree from `options.root` — including dotfiles and `/.meta/`,
|
|
60
|
-
* because sidecars live next to their data file
|
|
60
|
+
* because sidecars live next to their data file as `<name>.mtime` files.
|
|
61
61
|
* Unreadable entries are skipped; failures on individual files are collected
|
|
62
62
|
* in `failed` instead of aborting the walk.
|
|
63
63
|
*/
|
|
64
|
-
declare function purgeMtimeSidecars(fs: PurgeableFS, options?: PurgeMtimeOptions): Promise<MtimePurgeResult>;
|
|
64
|
+
declare function purgeMtimeSidecars(fs: PurgeableFS$1, options?: PurgeMtimeOptions): Promise<MtimePurgeResult>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* zen-fs-config — `.keep` placeholder cleanup (local primary only)
|
|
68
|
+
*
|
|
69
|
+
* Backends that cannot store empty directories (Git, RemoteStorage…) keep a
|
|
70
|
+
* directory alive with an internal `.keep` placeholder — exactly like the
|
|
71
|
+
* `.mtime` sidecar, this is backend-internal metadata that MUST be hidden from
|
|
72
|
+
* callers (see zen-fs-sync/docs/SyncableFS.md §1/§2).
|
|
73
|
+
*
|
|
74
|
+
* Older builds leaked `.keep` files into the local primary (IndexedDB on
|
|
75
|
+
* browser, Folder on Node), where they linger forever: the sync engine now
|
|
76
|
+
* skips `.keep` paths on both sides, so nothing ever removes them, and every
|
|
77
|
+
* walk re-creates confusion about which directories are "real".
|
|
78
|
+
*
|
|
79
|
+
* This module walks the local primary and deletes those leaked `.keep` files.
|
|
80
|
+
* It never touches replica (remote) backends: their placeholders are live
|
|
81
|
+
* metadata that keep real (empty) directories alive, and deleting them would
|
|
82
|
+
* lose directory structure.
|
|
83
|
+
*
|
|
84
|
+
* The intentional `.keep` under `/.meta/` (used to keep the backends directory
|
|
85
|
+
* alive) is protected and never deleted — see `protectedDirs`.
|
|
86
|
+
*/
|
|
87
|
+
/** Minimal async FS surface needed for the purge. */
|
|
88
|
+
interface PurgeableFS {
|
|
89
|
+
readdir(path: string): Promise<string[]>;
|
|
90
|
+
stat(path: string): Promise<{
|
|
91
|
+
mode?: number;
|
|
92
|
+
}>;
|
|
93
|
+
unlink(path: string): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
interface PurgeKeepOptions {
|
|
96
|
+
/** Root to scan. Default: `/` (the whole local primary). */
|
|
97
|
+
root?: string;
|
|
98
|
+
/** Report what would be deleted without unlinking anything. */
|
|
99
|
+
dryRun?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Directories (and their subtrees) whose `.keep` files are NEVER deleted.
|
|
102
|
+
* Defaults to `['/.meta']` to protect the intentional backends placeholder
|
|
103
|
+
* that keeps `/.meta/backends` alive.
|
|
104
|
+
*/
|
|
105
|
+
protectedDirs?: string[];
|
|
106
|
+
}
|
|
107
|
+
interface KeepPurgeResult {
|
|
108
|
+
/** Files visited during the walk (`.keep` included). */
|
|
109
|
+
scanned: number;
|
|
110
|
+
/** Placeholders deleted — or, in dryRun, that would be deleted. */
|
|
111
|
+
removed: string[];
|
|
112
|
+
/** Placeholders that could not be deleted (unlink failed). */
|
|
113
|
+
failed: string[];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* True when a file name is the internal `.keep` directory placeholder.
|
|
117
|
+
*
|
|
118
|
+
* Matches exactly `.keep` (the file name), which is the only form produced by
|
|
119
|
+
* the backends — never a suffix match, so `foo.keep` is left untouched.
|
|
120
|
+
*/
|
|
121
|
+
declare function isKeepFile(fileName: string): boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Delete every leaked `.keep` placeholder stored in a local primary backend.
|
|
124
|
+
*
|
|
125
|
+
* Walks the whole tree from `options.root` — including dotfiles and `/.meta/`
|
|
126
|
+
* entries — but skips any directory listed in `protectedDirs` (default
|
|
127
|
+
* `/.meta`) so the intentional backends placeholder survives. Unreadable
|
|
128
|
+
* entries are skipped; failures on individual files are collected in `failed`
|
|
129
|
+
* instead of aborting the walk.
|
|
130
|
+
*/
|
|
131
|
+
declare function purgeKeepFiles(fs: PurgeableFS, options?: PurgeKeepOptions): Promise<KeepPurgeResult>;
|
|
65
132
|
|
|
66
133
|
/** A single backend in the topology. */
|
|
67
134
|
interface BackendDescriptor {
|
|
@@ -229,6 +296,30 @@ interface ConfigRepoOptions {
|
|
|
229
296
|
* Default: `true`. Pass `false` to skip the (local-only) startup sweep.
|
|
230
297
|
*/
|
|
231
298
|
purgeMtimeSidecars?: boolean;
|
|
299
|
+
/**
|
|
300
|
+
* Delete leaked `.keep` placeholders from the local primary on startup.
|
|
301
|
+
*
|
|
302
|
+
* Backends that cannot store empty directories (Gitee, RemoteStorage…) keep a
|
|
303
|
+
* directory alive with an internal `.keep` placeholder. Older builds copied
|
|
304
|
+
* those backend-internal files into the local primary, where sync ignores
|
|
305
|
+
* them — so they linger forever. The intentional `/.meta/backends/.keep` is
|
|
306
|
+
* always protected.
|
|
307
|
+
*
|
|
308
|
+
* Default: `true`. Pass `false` to skip the (local-only) startup sweep.
|
|
309
|
+
*/
|
|
310
|
+
purgeKeepFiles?: boolean;
|
|
311
|
+
/**
|
|
312
|
+
* Migrate legacy `.x.version` sidecars to the new `<name>.version` naming on
|
|
313
|
+
* startup (rename locally, delete the remote residual).
|
|
314
|
+
*
|
|
315
|
+
* Older builds stored version sidecars as hidden dotfiles (`.db.json.version`).
|
|
316
|
+
* They are now `<name>.version`. This one-time sweep renames existing legacy
|
|
317
|
+
* sidecars (preserving version history) and removes the old copy from every
|
|
318
|
+
* replica so sync does not pull it back.
|
|
319
|
+
*
|
|
320
|
+
* Default: `true`. Pass `false` to skip the startup sweep.
|
|
321
|
+
*/
|
|
322
|
+
migrateVersionSidecars?: boolean;
|
|
232
323
|
}
|
|
233
324
|
/** Type of sync group. */
|
|
234
325
|
type SyncGroupType = 'config-sync' | 'data-sync';
|
|
@@ -421,6 +512,17 @@ interface IConfigRepo {
|
|
|
421
512
|
root?: string;
|
|
422
513
|
dryRun?: boolean;
|
|
423
514
|
}): Promise<MtimePurgeResult>;
|
|
515
|
+
/**
|
|
516
|
+
* Delete leaked `.keep` placeholders from the local primary backend.
|
|
517
|
+
*
|
|
518
|
+
* Runs automatically on `createConfigRepo()` unless
|
|
519
|
+
* `options.purgeKeepFiles === false`. Call it manually to clean a
|
|
520
|
+
* long-lived store, or with `{ dryRun: true }` to only list them.
|
|
521
|
+
*/
|
|
522
|
+
purgeKeepFiles(options?: {
|
|
523
|
+
root?: string;
|
|
524
|
+
dryRun?: boolean;
|
|
525
|
+
}): Promise<KeepPurgeResult>;
|
|
424
526
|
/**
|
|
425
527
|
* Create a data-sync group for this app.
|
|
426
528
|
* Each backend can optionally reference a config-sync backend's account
|
|
@@ -585,6 +687,71 @@ declare function getAccountFields(type: string): string[];
|
|
|
585
687
|
declare function mergeAccountFields(targetType: string, sourceOptions: Record<string, unknown>, targetOptions: Record<string, unknown>): Record<string, unknown>;
|
|
586
688
|
declare function wrapZenFSFileSystem(config: any): Promise<BackendInstance>;
|
|
587
689
|
|
|
690
|
+
/**
|
|
691
|
+
* zen-fs-config — Legacy Version Sidecar Migration
|
|
692
|
+
*
|
|
693
|
+
* Version sidecars used to be stored as hidden dotfiles:
|
|
694
|
+
*
|
|
695
|
+
* config file: /app-a/db.json
|
|
696
|
+
* version file: /app-a/.db.json.version (OLD — starts with `.`)
|
|
697
|
+
*
|
|
698
|
+
* They are now stored as `<name>.version` (no leading dot):
|
|
699
|
+
*
|
|
700
|
+
* version file: /app-a/db.json.version (NEW)
|
|
701
|
+
*
|
|
702
|
+
* This module migrates existing legacy sidecars to the new naming at startup,
|
|
703
|
+
* preserving version history (the file content is byte-identical after rename).
|
|
704
|
+
*
|
|
705
|
+
* Ambiguity safety: a legacy path `.x.version` could in theory belong to either
|
|
706
|
+
* a dotfile config `.x` (whose version is unchanged by the rename) or a
|
|
707
|
+
* non-dot config `x` (old naming). We resolve it by probing the directory:
|
|
708
|
+
* - `.x` exists → it's the dotfile config's version → NEW path == legacy → skip
|
|
709
|
+
* - `x` exists → rename legacy `.x.version` → `x.version`
|
|
710
|
+
* - neither → orphan (config gone) → delete
|
|
711
|
+
*
|
|
712
|
+
* We also delete the remote residual legacy path from every replica so the
|
|
713
|
+
* sync engine does not pull the old sidecar back (zen-fs-sync syncs `.version`
|
|
714
|
+
* files on both ends).
|
|
715
|
+
*/
|
|
716
|
+
/** Minimal async FS surface needed to read/rename/delete on the local primary. */
|
|
717
|
+
interface MigrateLocalFS {
|
|
718
|
+
readdir(path: string): Promise<string[]>;
|
|
719
|
+
stat(path: string): Promise<{
|
|
720
|
+
mode?: number;
|
|
721
|
+
}>;
|
|
722
|
+
readFile(path: string, encoding?: string): Promise<Uint8Array | string>;
|
|
723
|
+
writeFile(path: string, data: Uint8Array): Promise<void>;
|
|
724
|
+
unlink(path: string): Promise<void>;
|
|
725
|
+
}
|
|
726
|
+
/** Minimal async FS surface needed to delete remote residual legacy sidecars. */
|
|
727
|
+
interface MigrateReplicaFS {
|
|
728
|
+
unlink(path: string): Promise<void>;
|
|
729
|
+
}
|
|
730
|
+
interface VersionMigrationOptions {
|
|
731
|
+
/** Root to scan. Default: `/` (the whole local primary). */
|
|
732
|
+
root?: string;
|
|
733
|
+
/** Report what would change without touching anything. */
|
|
734
|
+
dryRun?: boolean;
|
|
735
|
+
/** Replica backends whose legacy sidecars should be deleted. */
|
|
736
|
+
replicas?: MigrateReplicaFS[];
|
|
737
|
+
}
|
|
738
|
+
interface VersionMigrationResult {
|
|
739
|
+
/** Local legacy sidecars renamed to the new naming. */
|
|
740
|
+
renamed: string[];
|
|
741
|
+
/** Local orphan legacy sidecars deleted (no owning config). */
|
|
742
|
+
deleted: string[];
|
|
743
|
+
/** Remote residual legacy sidecars deleted (best-effort). */
|
|
744
|
+
remoteDeleted: string[];
|
|
745
|
+
/** Paths that failed to migrate. */
|
|
746
|
+
failed: string[];
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Walk `fs` from `options.root` and migrate every legacy `.x.version` /
|
|
750
|
+
* `.x.version.mtime` sidecar to the new `<name>.version` / `<name>.version.mtime`
|
|
751
|
+
* naming. Best-effort: failures are collected in `failed`, never thrown.
|
|
752
|
+
*/
|
|
753
|
+
declare function migrateVersionSidecars(fs: MigrateLocalFS, options?: VersionMigrationOptions): Promise<VersionMigrationResult>;
|
|
754
|
+
|
|
588
755
|
/**
|
|
589
756
|
* zen-fs-config — ConfigRepo Implementation
|
|
590
757
|
*
|
|
@@ -690,6 +857,28 @@ declare class ConfigRepo implements IConfigRepo {
|
|
|
690
857
|
* @param options.dryRun List the sidecars without deleting them.
|
|
691
858
|
*/
|
|
692
859
|
purgeMtimeSidecars(options?: PurgeMtimeOptions): Promise<MtimePurgeResult>;
|
|
860
|
+
/**
|
|
861
|
+
* Delete leaked `.keep` placeholders from the local primary backend.
|
|
862
|
+
*
|
|
863
|
+
* Backends that cannot store empty directories (Gitee, RemoteStorage…) keep a
|
|
864
|
+
* directory alive with an internal `.keep` placeholder. Older builds copied
|
|
865
|
+
* those backend-internal files into the local primary, where sync ignores
|
|
866
|
+
* them — so they linger forever. `/.meta/backends/.keep` (intentional) is
|
|
867
|
+
* protected and never removed.
|
|
868
|
+
*
|
|
869
|
+
* @param options.root Scan only this subtree (default `/`).
|
|
870
|
+
* @param options.dryRun List the placeholders without deleting them.
|
|
871
|
+
*/
|
|
872
|
+
purgeKeepFiles(options?: PurgeKeepOptions): Promise<KeepPurgeResult>;
|
|
873
|
+
/**
|
|
874
|
+
* Migrate legacy `.x.version` sidecars (old dotfile naming) to the new
|
|
875
|
+
* `<name>.version` naming. Renames local sidecars (preserving content/history)
|
|
876
|
+
* and deletes the residual legacy copy from every replica backend so sync
|
|
877
|
+
* does not pull it back. Best-effort: failures are logged, never thrown.
|
|
878
|
+
*
|
|
879
|
+
* Runs once at startup (see createConfigRepo). Safe to call again.
|
|
880
|
+
*/
|
|
881
|
+
migrateLegacyVersionSidecars(): Promise<VersionMigrationResult>;
|
|
693
882
|
/**
|
|
694
883
|
* Perform a full sync + dedup cycle without the watch snapshot cache.
|
|
695
884
|
* Used by createConfigRepo to pull remote-only files (like duplicate
|
|
@@ -876,24 +1065,35 @@ declare function connect(appId: string, options?: ConnectOptions): Promise<Conne
|
|
|
876
1065
|
/**
|
|
877
1066
|
* zen-fs-config — Sidecar Version File Management
|
|
878
1067
|
*
|
|
879
|
-
* Each config file has a companion
|
|
880
|
-
* detection and conflict resolution.
|
|
1068
|
+
* Each config file has a companion `.version` sidecar file for version-based
|
|
1069
|
+
* change detection and conflict resolution. The sidecar is named `<name>.version`
|
|
1070
|
+
* (NOT `.name.version`) so it is not a hidden dotfile.
|
|
881
1071
|
*
|
|
882
1072
|
* Config file: /app-a/db.json
|
|
883
|
-
* Version file: /app-a
|
|
1073
|
+
* Version file: /app-a/db.json.version
|
|
884
1074
|
*/
|
|
885
1075
|
|
|
886
1076
|
/**
|
|
887
1077
|
* Compute the sidecar version file path from a config file path.
|
|
888
1078
|
*
|
|
889
|
-
* /app-a/db.json → /app-a
|
|
890
|
-
* /shared/flags.json → /shared
|
|
891
|
-
* /nodes/s1/env.json → /nodes/s1
|
|
1079
|
+
* /app-a/db.json → /app-a/db.json.version
|
|
1080
|
+
* /shared/flags.json → /shared/flags.json.version
|
|
1081
|
+
* /nodes/s1/env.json → /nodes/s1/env.json.version
|
|
892
1082
|
*
|
|
893
1083
|
* Returns null for files that are already version sidecars (.version files),
|
|
894
|
-
* to prevent creating version-of-version files (e.g.
|
|
1084
|
+
* to prevent creating version-of-version files (e.g. db.json.version.version).
|
|
895
1085
|
*/
|
|
896
1086
|
declare function versionPathFor(configFilePath: string): string | null;
|
|
1087
|
+
/**
|
|
1088
|
+
* Compute the *legacy* (pre-dotfile-removal) version sidecar path for a config
|
|
1089
|
+
* file. Returns the old `.${name}.version` form for non-dot configs, or `null`
|
|
1090
|
+
* for dotfile configs (whose version path is unchanged) and for version files
|
|
1091
|
+
* themselves.
|
|
1092
|
+
*
|
|
1093
|
+
* Used as a read fallback so existing history in `.db.json.version` is still
|
|
1094
|
+
* found after the naming scheme changed to `db.json.version`.
|
|
1095
|
+
*/
|
|
1096
|
+
declare function legacyVersionPathFor(configFilePath: string): string | null;
|
|
897
1097
|
/**
|
|
898
1098
|
* Compute SHA-256 hash of a Uint8Array.
|
|
899
1099
|
* Returns "sha256:" prefix + hex digest.
|
|
@@ -923,4 +1123,4 @@ declare function incrementVersion(fs: SyncableFS, configFilePath: string, newCon
|
|
|
923
1123
|
*/
|
|
924
1124
|
declare function verifyOrRepairVersion(fs: SyncableFS, configFilePath: string, author: string): Promise<VersionMeta | null>;
|
|
925
1125
|
|
|
926
|
-
export { type AppDataBackendDescriptor, type AppDataGroup, type AppDataGroupDescriptor, type BackendDescriptor, type BackendFactory, type BackendInstance, type BackendMetadata, type BackendParamDef, type BackendsMeta, type CacheOptions, ConfigRepo, type ConfigRepoOptions, type ConfigSerializer, type ConflictArchive, type ConflictInfo, type ConnectOptions, type ConnectResult, FolderStore, type IConfigRepo, LOCAL_IDB_BACKEND_ID, type MtimePurgeResult, type PurgeMtimeOptions, type PurgeableFS, type SyncGroupType, type TombstoneMeta, type VersionMeta, configKeyToFilePath, connect, createBackend, createConfigRepo, createSerializerChain, getAccountFields, getBackendMetadata, getExtension, hasBackend, incrementVersion, isBrowserEnv, isMtimeSidecar, listBackendMetadata, listBackends, localPrimaryType, mergeAccountFields, purgeMtimeSidecars, readVersion, registerBackend, registerFolderBackend, resolveLocalPrimary, sha256, unregisterBackend, verifyOrRepairVersion, versionPathFor, wrapZenFSFileSystem, writeVersion };
|
|
1126
|
+
export { type AppDataBackendDescriptor, type AppDataGroup, type AppDataGroupDescriptor, type BackendDescriptor, type BackendFactory, type BackendInstance, type BackendMetadata, type BackendParamDef, type BackendsMeta, type CacheOptions, ConfigRepo, type ConfigRepoOptions, type ConfigSerializer, type ConflictArchive, type ConflictInfo, type ConnectOptions, type ConnectResult, FolderStore, type IConfigRepo, type KeepPurgeResult, LOCAL_IDB_BACKEND_ID, type MigrateLocalFS, type MigrateReplicaFS, type MtimePurgeResult, type PurgeKeepOptions, type PurgeMtimeOptions, type PurgeableFS$1 as PurgeableFS, type SyncGroupType, type TombstoneMeta, type VersionMeta, type VersionMigrationOptions, type VersionMigrationResult, configKeyToFilePath, connect, createBackend, createConfigRepo, createSerializerChain, getAccountFields, getBackendMetadata, getExtension, hasBackend, incrementVersion, isBrowserEnv, isKeepFile, isMtimeSidecar, legacyVersionPathFor, listBackendMetadata, listBackends, localPrimaryType, mergeAccountFields, migrateVersionSidecars, purgeKeepFiles, purgeMtimeSidecars, readVersion, registerBackend, registerFolderBackend, resolveLocalPrimary, sha256, unregisterBackend, verifyOrRepairVersion, versionPathFor, wrapZenFSFileSystem, writeVersion };
|
package/dist/index.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { SyncMapStore, SyncMapTransaction } from '@zenfs/core';
|
|
|
25
25
|
* deleting them would cost network calls and lose mtime precision.
|
|
26
26
|
*/
|
|
27
27
|
/** Minimal async FS surface needed for the purge. */
|
|
28
|
-
interface PurgeableFS {
|
|
28
|
+
interface PurgeableFS$1 {
|
|
29
29
|
readdir(path: string): Promise<string[]>;
|
|
30
30
|
stat(path: string): Promise<{
|
|
31
31
|
mode?: number;
|
|
@@ -57,11 +57,78 @@ declare function isMtimeSidecar(fileName: string): boolean;
|
|
|
57
57
|
* Delete every `.mtime` sidecar stored in a local primary backend.
|
|
58
58
|
*
|
|
59
59
|
* Walks the whole tree from `options.root` — including dotfiles and `/.meta/`,
|
|
60
|
-
* because sidecars live next to their data file
|
|
60
|
+
* because sidecars live next to their data file as `<name>.mtime` files.
|
|
61
61
|
* Unreadable entries are skipped; failures on individual files are collected
|
|
62
62
|
* in `failed` instead of aborting the walk.
|
|
63
63
|
*/
|
|
64
|
-
declare function purgeMtimeSidecars(fs: PurgeableFS, options?: PurgeMtimeOptions): Promise<MtimePurgeResult>;
|
|
64
|
+
declare function purgeMtimeSidecars(fs: PurgeableFS$1, options?: PurgeMtimeOptions): Promise<MtimePurgeResult>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* zen-fs-config — `.keep` placeholder cleanup (local primary only)
|
|
68
|
+
*
|
|
69
|
+
* Backends that cannot store empty directories (Git, RemoteStorage…) keep a
|
|
70
|
+
* directory alive with an internal `.keep` placeholder — exactly like the
|
|
71
|
+
* `.mtime` sidecar, this is backend-internal metadata that MUST be hidden from
|
|
72
|
+
* callers (see zen-fs-sync/docs/SyncableFS.md §1/§2).
|
|
73
|
+
*
|
|
74
|
+
* Older builds leaked `.keep` files into the local primary (IndexedDB on
|
|
75
|
+
* browser, Folder on Node), where they linger forever: the sync engine now
|
|
76
|
+
* skips `.keep` paths on both sides, so nothing ever removes them, and every
|
|
77
|
+
* walk re-creates confusion about which directories are "real".
|
|
78
|
+
*
|
|
79
|
+
* This module walks the local primary and deletes those leaked `.keep` files.
|
|
80
|
+
* It never touches replica (remote) backends: their placeholders are live
|
|
81
|
+
* metadata that keep real (empty) directories alive, and deleting them would
|
|
82
|
+
* lose directory structure.
|
|
83
|
+
*
|
|
84
|
+
* The intentional `.keep` under `/.meta/` (used to keep the backends directory
|
|
85
|
+
* alive) is protected and never deleted — see `protectedDirs`.
|
|
86
|
+
*/
|
|
87
|
+
/** Minimal async FS surface needed for the purge. */
|
|
88
|
+
interface PurgeableFS {
|
|
89
|
+
readdir(path: string): Promise<string[]>;
|
|
90
|
+
stat(path: string): Promise<{
|
|
91
|
+
mode?: number;
|
|
92
|
+
}>;
|
|
93
|
+
unlink(path: string): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
interface PurgeKeepOptions {
|
|
96
|
+
/** Root to scan. Default: `/` (the whole local primary). */
|
|
97
|
+
root?: string;
|
|
98
|
+
/** Report what would be deleted without unlinking anything. */
|
|
99
|
+
dryRun?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Directories (and their subtrees) whose `.keep` files are NEVER deleted.
|
|
102
|
+
* Defaults to `['/.meta']` to protect the intentional backends placeholder
|
|
103
|
+
* that keeps `/.meta/backends` alive.
|
|
104
|
+
*/
|
|
105
|
+
protectedDirs?: string[];
|
|
106
|
+
}
|
|
107
|
+
interface KeepPurgeResult {
|
|
108
|
+
/** Files visited during the walk (`.keep` included). */
|
|
109
|
+
scanned: number;
|
|
110
|
+
/** Placeholders deleted — or, in dryRun, that would be deleted. */
|
|
111
|
+
removed: string[];
|
|
112
|
+
/** Placeholders that could not be deleted (unlink failed). */
|
|
113
|
+
failed: string[];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* True when a file name is the internal `.keep` directory placeholder.
|
|
117
|
+
*
|
|
118
|
+
* Matches exactly `.keep` (the file name), which is the only form produced by
|
|
119
|
+
* the backends — never a suffix match, so `foo.keep` is left untouched.
|
|
120
|
+
*/
|
|
121
|
+
declare function isKeepFile(fileName: string): boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Delete every leaked `.keep` placeholder stored in a local primary backend.
|
|
124
|
+
*
|
|
125
|
+
* Walks the whole tree from `options.root` — including dotfiles and `/.meta/`
|
|
126
|
+
* entries — but skips any directory listed in `protectedDirs` (default
|
|
127
|
+
* `/.meta`) so the intentional backends placeholder survives. Unreadable
|
|
128
|
+
* entries are skipped; failures on individual files are collected in `failed`
|
|
129
|
+
* instead of aborting the walk.
|
|
130
|
+
*/
|
|
131
|
+
declare function purgeKeepFiles(fs: PurgeableFS, options?: PurgeKeepOptions): Promise<KeepPurgeResult>;
|
|
65
132
|
|
|
66
133
|
/** A single backend in the topology. */
|
|
67
134
|
interface BackendDescriptor {
|
|
@@ -229,6 +296,30 @@ interface ConfigRepoOptions {
|
|
|
229
296
|
* Default: `true`. Pass `false` to skip the (local-only) startup sweep.
|
|
230
297
|
*/
|
|
231
298
|
purgeMtimeSidecars?: boolean;
|
|
299
|
+
/**
|
|
300
|
+
* Delete leaked `.keep` placeholders from the local primary on startup.
|
|
301
|
+
*
|
|
302
|
+
* Backends that cannot store empty directories (Gitee, RemoteStorage…) keep a
|
|
303
|
+
* directory alive with an internal `.keep` placeholder. Older builds copied
|
|
304
|
+
* those backend-internal files into the local primary, where sync ignores
|
|
305
|
+
* them — so they linger forever. The intentional `/.meta/backends/.keep` is
|
|
306
|
+
* always protected.
|
|
307
|
+
*
|
|
308
|
+
* Default: `true`. Pass `false` to skip the (local-only) startup sweep.
|
|
309
|
+
*/
|
|
310
|
+
purgeKeepFiles?: boolean;
|
|
311
|
+
/**
|
|
312
|
+
* Migrate legacy `.x.version` sidecars to the new `<name>.version` naming on
|
|
313
|
+
* startup (rename locally, delete the remote residual).
|
|
314
|
+
*
|
|
315
|
+
* Older builds stored version sidecars as hidden dotfiles (`.db.json.version`).
|
|
316
|
+
* They are now `<name>.version`. This one-time sweep renames existing legacy
|
|
317
|
+
* sidecars (preserving version history) and removes the old copy from every
|
|
318
|
+
* replica so sync does not pull it back.
|
|
319
|
+
*
|
|
320
|
+
* Default: `true`. Pass `false` to skip the startup sweep.
|
|
321
|
+
*/
|
|
322
|
+
migrateVersionSidecars?: boolean;
|
|
232
323
|
}
|
|
233
324
|
/** Type of sync group. */
|
|
234
325
|
type SyncGroupType = 'config-sync' | 'data-sync';
|
|
@@ -421,6 +512,17 @@ interface IConfigRepo {
|
|
|
421
512
|
root?: string;
|
|
422
513
|
dryRun?: boolean;
|
|
423
514
|
}): Promise<MtimePurgeResult>;
|
|
515
|
+
/**
|
|
516
|
+
* Delete leaked `.keep` placeholders from the local primary backend.
|
|
517
|
+
*
|
|
518
|
+
* Runs automatically on `createConfigRepo()` unless
|
|
519
|
+
* `options.purgeKeepFiles === false`. Call it manually to clean a
|
|
520
|
+
* long-lived store, or with `{ dryRun: true }` to only list them.
|
|
521
|
+
*/
|
|
522
|
+
purgeKeepFiles(options?: {
|
|
523
|
+
root?: string;
|
|
524
|
+
dryRun?: boolean;
|
|
525
|
+
}): Promise<KeepPurgeResult>;
|
|
424
526
|
/**
|
|
425
527
|
* Create a data-sync group for this app.
|
|
426
528
|
* Each backend can optionally reference a config-sync backend's account
|
|
@@ -585,6 +687,71 @@ declare function getAccountFields(type: string): string[];
|
|
|
585
687
|
declare function mergeAccountFields(targetType: string, sourceOptions: Record<string, unknown>, targetOptions: Record<string, unknown>): Record<string, unknown>;
|
|
586
688
|
declare function wrapZenFSFileSystem(config: any): Promise<BackendInstance>;
|
|
587
689
|
|
|
690
|
+
/**
|
|
691
|
+
* zen-fs-config — Legacy Version Sidecar Migration
|
|
692
|
+
*
|
|
693
|
+
* Version sidecars used to be stored as hidden dotfiles:
|
|
694
|
+
*
|
|
695
|
+
* config file: /app-a/db.json
|
|
696
|
+
* version file: /app-a/.db.json.version (OLD — starts with `.`)
|
|
697
|
+
*
|
|
698
|
+
* They are now stored as `<name>.version` (no leading dot):
|
|
699
|
+
*
|
|
700
|
+
* version file: /app-a/db.json.version (NEW)
|
|
701
|
+
*
|
|
702
|
+
* This module migrates existing legacy sidecars to the new naming at startup,
|
|
703
|
+
* preserving version history (the file content is byte-identical after rename).
|
|
704
|
+
*
|
|
705
|
+
* Ambiguity safety: a legacy path `.x.version` could in theory belong to either
|
|
706
|
+
* a dotfile config `.x` (whose version is unchanged by the rename) or a
|
|
707
|
+
* non-dot config `x` (old naming). We resolve it by probing the directory:
|
|
708
|
+
* - `.x` exists → it's the dotfile config's version → NEW path == legacy → skip
|
|
709
|
+
* - `x` exists → rename legacy `.x.version` → `x.version`
|
|
710
|
+
* - neither → orphan (config gone) → delete
|
|
711
|
+
*
|
|
712
|
+
* We also delete the remote residual legacy path from every replica so the
|
|
713
|
+
* sync engine does not pull the old sidecar back (zen-fs-sync syncs `.version`
|
|
714
|
+
* files on both ends).
|
|
715
|
+
*/
|
|
716
|
+
/** Minimal async FS surface needed to read/rename/delete on the local primary. */
|
|
717
|
+
interface MigrateLocalFS {
|
|
718
|
+
readdir(path: string): Promise<string[]>;
|
|
719
|
+
stat(path: string): Promise<{
|
|
720
|
+
mode?: number;
|
|
721
|
+
}>;
|
|
722
|
+
readFile(path: string, encoding?: string): Promise<Uint8Array | string>;
|
|
723
|
+
writeFile(path: string, data: Uint8Array): Promise<void>;
|
|
724
|
+
unlink(path: string): Promise<void>;
|
|
725
|
+
}
|
|
726
|
+
/** Minimal async FS surface needed to delete remote residual legacy sidecars. */
|
|
727
|
+
interface MigrateReplicaFS {
|
|
728
|
+
unlink(path: string): Promise<void>;
|
|
729
|
+
}
|
|
730
|
+
interface VersionMigrationOptions {
|
|
731
|
+
/** Root to scan. Default: `/` (the whole local primary). */
|
|
732
|
+
root?: string;
|
|
733
|
+
/** Report what would change without touching anything. */
|
|
734
|
+
dryRun?: boolean;
|
|
735
|
+
/** Replica backends whose legacy sidecars should be deleted. */
|
|
736
|
+
replicas?: MigrateReplicaFS[];
|
|
737
|
+
}
|
|
738
|
+
interface VersionMigrationResult {
|
|
739
|
+
/** Local legacy sidecars renamed to the new naming. */
|
|
740
|
+
renamed: string[];
|
|
741
|
+
/** Local orphan legacy sidecars deleted (no owning config). */
|
|
742
|
+
deleted: string[];
|
|
743
|
+
/** Remote residual legacy sidecars deleted (best-effort). */
|
|
744
|
+
remoteDeleted: string[];
|
|
745
|
+
/** Paths that failed to migrate. */
|
|
746
|
+
failed: string[];
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Walk `fs` from `options.root` and migrate every legacy `.x.version` /
|
|
750
|
+
* `.x.version.mtime` sidecar to the new `<name>.version` / `<name>.version.mtime`
|
|
751
|
+
* naming. Best-effort: failures are collected in `failed`, never thrown.
|
|
752
|
+
*/
|
|
753
|
+
declare function migrateVersionSidecars(fs: MigrateLocalFS, options?: VersionMigrationOptions): Promise<VersionMigrationResult>;
|
|
754
|
+
|
|
588
755
|
/**
|
|
589
756
|
* zen-fs-config — ConfigRepo Implementation
|
|
590
757
|
*
|
|
@@ -690,6 +857,28 @@ declare class ConfigRepo implements IConfigRepo {
|
|
|
690
857
|
* @param options.dryRun List the sidecars without deleting them.
|
|
691
858
|
*/
|
|
692
859
|
purgeMtimeSidecars(options?: PurgeMtimeOptions): Promise<MtimePurgeResult>;
|
|
860
|
+
/**
|
|
861
|
+
* Delete leaked `.keep` placeholders from the local primary backend.
|
|
862
|
+
*
|
|
863
|
+
* Backends that cannot store empty directories (Gitee, RemoteStorage…) keep a
|
|
864
|
+
* directory alive with an internal `.keep` placeholder. Older builds copied
|
|
865
|
+
* those backend-internal files into the local primary, where sync ignores
|
|
866
|
+
* them — so they linger forever. `/.meta/backends/.keep` (intentional) is
|
|
867
|
+
* protected and never removed.
|
|
868
|
+
*
|
|
869
|
+
* @param options.root Scan only this subtree (default `/`).
|
|
870
|
+
* @param options.dryRun List the placeholders without deleting them.
|
|
871
|
+
*/
|
|
872
|
+
purgeKeepFiles(options?: PurgeKeepOptions): Promise<KeepPurgeResult>;
|
|
873
|
+
/**
|
|
874
|
+
* Migrate legacy `.x.version` sidecars (old dotfile naming) to the new
|
|
875
|
+
* `<name>.version` naming. Renames local sidecars (preserving content/history)
|
|
876
|
+
* and deletes the residual legacy copy from every replica backend so sync
|
|
877
|
+
* does not pull it back. Best-effort: failures are logged, never thrown.
|
|
878
|
+
*
|
|
879
|
+
* Runs once at startup (see createConfigRepo). Safe to call again.
|
|
880
|
+
*/
|
|
881
|
+
migrateLegacyVersionSidecars(): Promise<VersionMigrationResult>;
|
|
693
882
|
/**
|
|
694
883
|
* Perform a full sync + dedup cycle without the watch snapshot cache.
|
|
695
884
|
* Used by createConfigRepo to pull remote-only files (like duplicate
|
|
@@ -876,24 +1065,35 @@ declare function connect(appId: string, options?: ConnectOptions): Promise<Conne
|
|
|
876
1065
|
/**
|
|
877
1066
|
* zen-fs-config — Sidecar Version File Management
|
|
878
1067
|
*
|
|
879
|
-
* Each config file has a companion
|
|
880
|
-
* detection and conflict resolution.
|
|
1068
|
+
* Each config file has a companion `.version` sidecar file for version-based
|
|
1069
|
+
* change detection and conflict resolution. The sidecar is named `<name>.version`
|
|
1070
|
+
* (NOT `.name.version`) so it is not a hidden dotfile.
|
|
881
1071
|
*
|
|
882
1072
|
* Config file: /app-a/db.json
|
|
883
|
-
* Version file: /app-a
|
|
1073
|
+
* Version file: /app-a/db.json.version
|
|
884
1074
|
*/
|
|
885
1075
|
|
|
886
1076
|
/**
|
|
887
1077
|
* Compute the sidecar version file path from a config file path.
|
|
888
1078
|
*
|
|
889
|
-
* /app-a/db.json → /app-a
|
|
890
|
-
* /shared/flags.json → /shared
|
|
891
|
-
* /nodes/s1/env.json → /nodes/s1
|
|
1079
|
+
* /app-a/db.json → /app-a/db.json.version
|
|
1080
|
+
* /shared/flags.json → /shared/flags.json.version
|
|
1081
|
+
* /nodes/s1/env.json → /nodes/s1/env.json.version
|
|
892
1082
|
*
|
|
893
1083
|
* Returns null for files that are already version sidecars (.version files),
|
|
894
|
-
* to prevent creating version-of-version files (e.g.
|
|
1084
|
+
* to prevent creating version-of-version files (e.g. db.json.version.version).
|
|
895
1085
|
*/
|
|
896
1086
|
declare function versionPathFor(configFilePath: string): string | null;
|
|
1087
|
+
/**
|
|
1088
|
+
* Compute the *legacy* (pre-dotfile-removal) version sidecar path for a config
|
|
1089
|
+
* file. Returns the old `.${name}.version` form for non-dot configs, or `null`
|
|
1090
|
+
* for dotfile configs (whose version path is unchanged) and for version files
|
|
1091
|
+
* themselves.
|
|
1092
|
+
*
|
|
1093
|
+
* Used as a read fallback so existing history in `.db.json.version` is still
|
|
1094
|
+
* found after the naming scheme changed to `db.json.version`.
|
|
1095
|
+
*/
|
|
1096
|
+
declare function legacyVersionPathFor(configFilePath: string): string | null;
|
|
897
1097
|
/**
|
|
898
1098
|
* Compute SHA-256 hash of a Uint8Array.
|
|
899
1099
|
* Returns "sha256:" prefix + hex digest.
|
|
@@ -923,4 +1123,4 @@ declare function incrementVersion(fs: SyncableFS, configFilePath: string, newCon
|
|
|
923
1123
|
*/
|
|
924
1124
|
declare function verifyOrRepairVersion(fs: SyncableFS, configFilePath: string, author: string): Promise<VersionMeta | null>;
|
|
925
1125
|
|
|
926
|
-
export { type AppDataBackendDescriptor, type AppDataGroup, type AppDataGroupDescriptor, type BackendDescriptor, type BackendFactory, type BackendInstance, type BackendMetadata, type BackendParamDef, type BackendsMeta, type CacheOptions, ConfigRepo, type ConfigRepoOptions, type ConfigSerializer, type ConflictArchive, type ConflictInfo, type ConnectOptions, type ConnectResult, FolderStore, type IConfigRepo, LOCAL_IDB_BACKEND_ID, type MtimePurgeResult, type PurgeMtimeOptions, type PurgeableFS, type SyncGroupType, type TombstoneMeta, type VersionMeta, configKeyToFilePath, connect, createBackend, createConfigRepo, createSerializerChain, getAccountFields, getBackendMetadata, getExtension, hasBackend, incrementVersion, isBrowserEnv, isMtimeSidecar, listBackendMetadata, listBackends, localPrimaryType, mergeAccountFields, purgeMtimeSidecars, readVersion, registerBackend, registerFolderBackend, resolveLocalPrimary, sha256, unregisterBackend, verifyOrRepairVersion, versionPathFor, wrapZenFSFileSystem, writeVersion };
|
|
1126
|
+
export { type AppDataBackendDescriptor, type AppDataGroup, type AppDataGroupDescriptor, type BackendDescriptor, type BackendFactory, type BackendInstance, type BackendMetadata, type BackendParamDef, type BackendsMeta, type CacheOptions, ConfigRepo, type ConfigRepoOptions, type ConfigSerializer, type ConflictArchive, type ConflictInfo, type ConnectOptions, type ConnectResult, FolderStore, type IConfigRepo, type KeepPurgeResult, LOCAL_IDB_BACKEND_ID, type MigrateLocalFS, type MigrateReplicaFS, type MtimePurgeResult, type PurgeKeepOptions, type PurgeMtimeOptions, type PurgeableFS$1 as PurgeableFS, type SyncGroupType, type TombstoneMeta, type VersionMeta, type VersionMigrationOptions, type VersionMigrationResult, configKeyToFilePath, connect, createBackend, createConfigRepo, createSerializerChain, getAccountFields, getBackendMetadata, getExtension, hasBackend, incrementVersion, isBrowserEnv, isKeepFile, isMtimeSidecar, legacyVersionPathFor, listBackendMetadata, listBackends, localPrimaryType, mergeAccountFields, migrateVersionSidecars, purgeKeepFiles, purgeMtimeSidecars, readVersion, registerBackend, registerFolderBackend, resolveLocalPrimary, sha256, unregisterBackend, verifyOrRepairVersion, versionPathFor, wrapZenFSFileSystem, writeVersion };
|