zen-fs-config 0.5.31 → 0.5.33
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 +202 -8
- package/dist/index.d.ts +202 -8
- package/dist/index.js +313 -4
- package/dist/index.mjs +309 -4
- package/dist/zen-fs-config.js +321 -16
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -4,6 +4,65 @@ import * as node_fs from 'node:fs';
|
|
|
4
4
|
import * as node_path from 'node:path';
|
|
5
5
|
import { SyncMapStore, SyncMapTransaction } from '@zenfs/core';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* zen-fs-config — mtime sidecar cleanup
|
|
9
|
+
*
|
|
10
|
+
* Backends that cannot store a precise mtime natively (RemoteStorage, Gitee,
|
|
11
|
+
* …) persist it in a `.mtime` sidecar next to each file
|
|
12
|
+
* (`data.json` → `.data.json.mtime`). A sidecar is backend-internal metadata:
|
|
13
|
+
* it belongs to the backend that produced it and must never be handed out by
|
|
14
|
+
* `readdir()`/`createSnapshot()`.
|
|
15
|
+
*
|
|
16
|
+
* Older builds did leak them, and once a sidecar lands in the local primary
|
|
17
|
+
* (IndexedDB on browser, Folder on Node) it stays there forever — zen-fs-sync
|
|
18
|
+
* deliberately skips `.mtime` paths on both sides, so nothing ever removes
|
|
19
|
+
* them, and every walk logs:
|
|
20
|
+
*
|
|
21
|
+
* [zen-fs-sync] mtime sidecar leaked from backend "local-idb" …
|
|
22
|
+
*
|
|
23
|
+
* This module walks the local primary and deletes those leaked files. It never
|
|
24
|
+
* touches replica (remote) backends: their sidecars are live metadata, and
|
|
25
|
+
* deleting them would cost network calls and lose mtime precision.
|
|
26
|
+
*/
|
|
27
|
+
/** Minimal async FS surface needed for the purge. */
|
|
28
|
+
interface PurgeableFS {
|
|
29
|
+
readdir(path: string): Promise<string[]>;
|
|
30
|
+
stat(path: string): Promise<{
|
|
31
|
+
mode?: number;
|
|
32
|
+
}>;
|
|
33
|
+
unlink(path: string): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
interface PurgeMtimeOptions {
|
|
36
|
+
/** Root to scan. Default: `/` (the whole local primary). */
|
|
37
|
+
root?: string;
|
|
38
|
+
/** Report what would be deleted without unlinking anything. */
|
|
39
|
+
dryRun?: boolean;
|
|
40
|
+
}
|
|
41
|
+
interface MtimePurgeResult {
|
|
42
|
+
/** Files visited during the walk (sidecars included). */
|
|
43
|
+
scanned: number;
|
|
44
|
+
/** Sidecars deleted — or, in dryRun, that would be deleted. */
|
|
45
|
+
removed: string[];
|
|
46
|
+
/** Sidecars that could not be deleted (unlink failed). */
|
|
47
|
+
failed: string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* True when a file name is an mtime sidecar.
|
|
51
|
+
*
|
|
52
|
+
* Uses the same rule as zen-fs-sync's walker (`path.endsWith('.mtime')`), so
|
|
53
|
+
* the pathological nested form (`.foo.mtime.mtime`) is covered too.
|
|
54
|
+
*/
|
|
55
|
+
declare function isMtimeSidecar(fileName: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Delete every `.mtime` sidecar stored in a local primary backend.
|
|
58
|
+
*
|
|
59
|
+
* Walks the whole tree from `options.root` — including dotfiles and `/.meta/`,
|
|
60
|
+
* because sidecars live next to their data file as `<name>.mtime` files.
|
|
61
|
+
* Unreadable entries are skipped; failures on individual files are collected
|
|
62
|
+
* in `failed` instead of aborting the walk.
|
|
63
|
+
*/
|
|
64
|
+
declare function purgeMtimeSidecars(fs: PurgeableFS, options?: PurgeMtimeOptions): Promise<MtimePurgeResult>;
|
|
65
|
+
|
|
7
66
|
/** A single backend in the topology. */
|
|
8
67
|
interface BackendDescriptor {
|
|
9
68
|
/** Unique identifier within this config repo (e.g., "local-idb"). */
|
|
@@ -159,6 +218,29 @@ interface ConfigRepoOptions {
|
|
|
159
218
|
* Default: 1800000 (30 minutes).
|
|
160
219
|
*/
|
|
161
220
|
syncPollIntervalMs?: number;
|
|
221
|
+
/**
|
|
222
|
+
* Delete leaked `.mtime` sidecars from the local primary on startup.
|
|
223
|
+
*
|
|
224
|
+
* Backends that keep a precise mtime out-of-band (RemoteStorage, Gitee…)
|
|
225
|
+
* write a `.mtime` file next to each data file. Older builds copied those
|
|
226
|
+
* backend-internal files into the local primary, where sync ignores them —
|
|
227
|
+
* so they linger forever and warn on every walk.
|
|
228
|
+
*
|
|
229
|
+
* Default: `true`. Pass `false` to skip the (local-only) startup sweep.
|
|
230
|
+
*/
|
|
231
|
+
purgeMtimeSidecars?: boolean;
|
|
232
|
+
/**
|
|
233
|
+
* Migrate legacy `.x.version` sidecars to the new `<name>.version` naming on
|
|
234
|
+
* startup (rename locally, delete the remote residual).
|
|
235
|
+
*
|
|
236
|
+
* Older builds stored version sidecars as hidden dotfiles (`.db.json.version`).
|
|
237
|
+
* They are now `<name>.version`. This one-time sweep renames existing legacy
|
|
238
|
+
* sidecars (preserving version history) and removes the old copy from every
|
|
239
|
+
* replica so sync does not pull it back.
|
|
240
|
+
*
|
|
241
|
+
* Default: `true`. Pass `false` to skip the startup sweep.
|
|
242
|
+
*/
|
|
243
|
+
migrateVersionSidecars?: boolean;
|
|
162
244
|
}
|
|
163
245
|
/** Type of sync group. */
|
|
164
246
|
type SyncGroupType = 'config-sync' | 'data-sync';
|
|
@@ -340,6 +422,17 @@ interface IConfigRepo {
|
|
|
340
422
|
* Called automatically by createConfigRepo() after setupSync().
|
|
341
423
|
*/
|
|
342
424
|
syncMetaToReplicas(): Promise<void>;
|
|
425
|
+
/**
|
|
426
|
+
* Delete leaked `.mtime` sidecars from the local primary backend.
|
|
427
|
+
*
|
|
428
|
+
* Runs automatically on `createConfigRepo()` unless
|
|
429
|
+
* `options.purgeMtimeSidecars === false`. Call it manually to clean a
|
|
430
|
+
* long-lived store, or with `{ dryRun: true }` to only list them.
|
|
431
|
+
*/
|
|
432
|
+
purgeMtimeSidecars(options?: {
|
|
433
|
+
root?: string;
|
|
434
|
+
dryRun?: boolean;
|
|
435
|
+
}): Promise<MtimePurgeResult>;
|
|
343
436
|
/**
|
|
344
437
|
* Create a data-sync group for this app.
|
|
345
438
|
* Each backend can optionally reference a config-sync backend's account
|
|
@@ -504,6 +597,71 @@ declare function getAccountFields(type: string): string[];
|
|
|
504
597
|
declare function mergeAccountFields(targetType: string, sourceOptions: Record<string, unknown>, targetOptions: Record<string, unknown>): Record<string, unknown>;
|
|
505
598
|
declare function wrapZenFSFileSystem(config: any): Promise<BackendInstance>;
|
|
506
599
|
|
|
600
|
+
/**
|
|
601
|
+
* zen-fs-config — Legacy Version Sidecar Migration
|
|
602
|
+
*
|
|
603
|
+
* Version sidecars used to be stored as hidden dotfiles:
|
|
604
|
+
*
|
|
605
|
+
* config file: /app-a/db.json
|
|
606
|
+
* version file: /app-a/.db.json.version (OLD — starts with `.`)
|
|
607
|
+
*
|
|
608
|
+
* They are now stored as `<name>.version` (no leading dot):
|
|
609
|
+
*
|
|
610
|
+
* version file: /app-a/db.json.version (NEW)
|
|
611
|
+
*
|
|
612
|
+
* This module migrates existing legacy sidecars to the new naming at startup,
|
|
613
|
+
* preserving version history (the file content is byte-identical after rename).
|
|
614
|
+
*
|
|
615
|
+
* Ambiguity safety: a legacy path `.x.version` could in theory belong to either
|
|
616
|
+
* a dotfile config `.x` (whose version is unchanged by the rename) or a
|
|
617
|
+
* non-dot config `x` (old naming). We resolve it by probing the directory:
|
|
618
|
+
* - `.x` exists → it's the dotfile config's version → NEW path == legacy → skip
|
|
619
|
+
* - `x` exists → rename legacy `.x.version` → `x.version`
|
|
620
|
+
* - neither → orphan (config gone) → delete
|
|
621
|
+
*
|
|
622
|
+
* We also delete the remote residual legacy path from every replica so the
|
|
623
|
+
* sync engine does not pull the old sidecar back (zen-fs-sync syncs `.version`
|
|
624
|
+
* files on both ends).
|
|
625
|
+
*/
|
|
626
|
+
/** Minimal async FS surface needed to read/rename/delete on the local primary. */
|
|
627
|
+
interface MigrateLocalFS {
|
|
628
|
+
readdir(path: string): Promise<string[]>;
|
|
629
|
+
stat(path: string): Promise<{
|
|
630
|
+
mode?: number;
|
|
631
|
+
}>;
|
|
632
|
+
readFile(path: string, encoding?: string): Promise<Uint8Array | string>;
|
|
633
|
+
writeFile(path: string, data: Uint8Array): Promise<void>;
|
|
634
|
+
unlink(path: string): Promise<void>;
|
|
635
|
+
}
|
|
636
|
+
/** Minimal async FS surface needed to delete remote residual legacy sidecars. */
|
|
637
|
+
interface MigrateReplicaFS {
|
|
638
|
+
unlink(path: string): Promise<void>;
|
|
639
|
+
}
|
|
640
|
+
interface VersionMigrationOptions {
|
|
641
|
+
/** Root to scan. Default: `/` (the whole local primary). */
|
|
642
|
+
root?: string;
|
|
643
|
+
/** Report what would change without touching anything. */
|
|
644
|
+
dryRun?: boolean;
|
|
645
|
+
/** Replica backends whose legacy sidecars should be deleted. */
|
|
646
|
+
replicas?: MigrateReplicaFS[];
|
|
647
|
+
}
|
|
648
|
+
interface VersionMigrationResult {
|
|
649
|
+
/** Local legacy sidecars renamed to the new naming. */
|
|
650
|
+
renamed: string[];
|
|
651
|
+
/** Local orphan legacy sidecars deleted (no owning config). */
|
|
652
|
+
deleted: string[];
|
|
653
|
+
/** Remote residual legacy sidecars deleted (best-effort). */
|
|
654
|
+
remoteDeleted: string[];
|
|
655
|
+
/** Paths that failed to migrate. */
|
|
656
|
+
failed: string[];
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Walk `fs` from `options.root` and migrate every legacy `.x.version` /
|
|
660
|
+
* `.x.version.mtime` sidecar to the new `<name>.version` / `<name>.version.mtime`
|
|
661
|
+
* naming. Best-effort: failures are collected in `failed`, never thrown.
|
|
662
|
+
*/
|
|
663
|
+
declare function migrateVersionSidecars(fs: MigrateLocalFS, options?: VersionMigrationOptions): Promise<VersionMigrationResult>;
|
|
664
|
+
|
|
507
665
|
/**
|
|
508
666
|
* zen-fs-config — ConfigRepo Implementation
|
|
509
667
|
*
|
|
@@ -593,6 +751,31 @@ declare class ConfigRepo implements IConfigRepo {
|
|
|
593
751
|
private safeExists;
|
|
594
752
|
/** Public wrapper for processTombstones — used by createConfigRepo. */
|
|
595
753
|
processTombstonesPublic(): Promise<void>;
|
|
754
|
+
/**
|
|
755
|
+
* Delete leaked `.mtime` sidecar files from the local primary backend
|
|
756
|
+
* (IndexedDB on browser, Folder on Node).
|
|
757
|
+
*
|
|
758
|
+
* Sidecars are produced by backends that keep a precise mtime out-of-band
|
|
759
|
+
* (RemoteStorage, Gitee…). Once one is copied into the local primary it is
|
|
760
|
+
* never removed: zen-fs-sync skips `.mtime` paths on both sides, so it is
|
|
761
|
+
* invisible to sync, and every walk warns about the leak.
|
|
762
|
+
*
|
|
763
|
+
* Only the local primary is scanned — replica sidecars are live metadata of
|
|
764
|
+
* the backend that owns them and must be left alone.
|
|
765
|
+
*
|
|
766
|
+
* @param options.root Scan only this subtree (default `/`).
|
|
767
|
+
* @param options.dryRun List the sidecars without deleting them.
|
|
768
|
+
*/
|
|
769
|
+
purgeMtimeSidecars(options?: PurgeMtimeOptions): Promise<MtimePurgeResult>;
|
|
770
|
+
/**
|
|
771
|
+
* Migrate legacy `.x.version` sidecars (old dotfile naming) to the new
|
|
772
|
+
* `<name>.version` naming. Renames local sidecars (preserving content/history)
|
|
773
|
+
* and deletes the residual legacy copy from every replica backend so sync
|
|
774
|
+
* does not pull it back. Best-effort: failures are logged, never thrown.
|
|
775
|
+
*
|
|
776
|
+
* Runs once at startup (see createConfigRepo). Safe to call again.
|
|
777
|
+
*/
|
|
778
|
+
migrateLegacyVersionSidecars(): Promise<VersionMigrationResult>;
|
|
596
779
|
/**
|
|
597
780
|
* Perform a full sync + dedup cycle without the watch snapshot cache.
|
|
598
781
|
* Used by createConfigRepo to pull remote-only files (like duplicate
|
|
@@ -779,24 +962,35 @@ declare function connect(appId: string, options?: ConnectOptions): Promise<Conne
|
|
|
779
962
|
/**
|
|
780
963
|
* zen-fs-config — Sidecar Version File Management
|
|
781
964
|
*
|
|
782
|
-
* Each config file has a companion
|
|
783
|
-
* detection and conflict resolution.
|
|
965
|
+
* Each config file has a companion `.version` sidecar file for version-based
|
|
966
|
+
* change detection and conflict resolution. The sidecar is named `<name>.version`
|
|
967
|
+
* (NOT `.name.version`) so it is not a hidden dotfile.
|
|
784
968
|
*
|
|
785
969
|
* Config file: /app-a/db.json
|
|
786
|
-
* Version file: /app-a
|
|
970
|
+
* Version file: /app-a/db.json.version
|
|
787
971
|
*/
|
|
788
972
|
|
|
789
973
|
/**
|
|
790
974
|
* Compute the sidecar version file path from a config file path.
|
|
791
975
|
*
|
|
792
|
-
* /app-a/db.json → /app-a
|
|
793
|
-
* /shared/flags.json → /shared
|
|
794
|
-
* /nodes/s1/env.json → /nodes/s1
|
|
976
|
+
* /app-a/db.json → /app-a/db.json.version
|
|
977
|
+
* /shared/flags.json → /shared/flags.json.version
|
|
978
|
+
* /nodes/s1/env.json → /nodes/s1/env.json.version
|
|
795
979
|
*
|
|
796
980
|
* Returns null for files that are already version sidecars (.version files),
|
|
797
|
-
* to prevent creating version-of-version files (e.g.
|
|
981
|
+
* to prevent creating version-of-version files (e.g. db.json.version.version).
|
|
798
982
|
*/
|
|
799
983
|
declare function versionPathFor(configFilePath: string): string | null;
|
|
984
|
+
/**
|
|
985
|
+
* Compute the *legacy* (pre-dotfile-removal) version sidecar path for a config
|
|
986
|
+
* file. Returns the old `.${name}.version` form for non-dot configs, or `null`
|
|
987
|
+
* for dotfile configs (whose version path is unchanged) and for version files
|
|
988
|
+
* themselves.
|
|
989
|
+
*
|
|
990
|
+
* Used as a read fallback so existing history in `.db.json.version` is still
|
|
991
|
+
* found after the naming scheme changed to `db.json.version`.
|
|
992
|
+
*/
|
|
993
|
+
declare function legacyVersionPathFor(configFilePath: string): string | null;
|
|
800
994
|
/**
|
|
801
995
|
* Compute SHA-256 hash of a Uint8Array.
|
|
802
996
|
* Returns "sha256:" prefix + hex digest.
|
|
@@ -826,4 +1020,4 @@ declare function incrementVersion(fs: SyncableFS, configFilePath: string, newCon
|
|
|
826
1020
|
*/
|
|
827
1021
|
declare function verifyOrRepairVersion(fs: SyncableFS, configFilePath: string, author: string): Promise<VersionMeta | null>;
|
|
828
1022
|
|
|
829
|
-
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 SyncGroupType, type TombstoneMeta, type VersionMeta, configKeyToFilePath, connect, createBackend, createConfigRepo, createSerializerChain, getAccountFields, getBackendMetadata, getExtension, hasBackend, incrementVersion, isBrowserEnv, listBackendMetadata, listBackends, localPrimaryType, mergeAccountFields, readVersion, registerBackend, registerFolderBackend, resolveLocalPrimary, sha256, unregisterBackend, verifyOrRepairVersion, versionPathFor, wrapZenFSFileSystem, writeVersion };
|
|
1023
|
+
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 MigrateLocalFS, type MigrateReplicaFS, type MtimePurgeResult, type PurgeMtimeOptions, type PurgeableFS, type SyncGroupType, type TombstoneMeta, type VersionMeta, type VersionMigrationOptions, type VersionMigrationResult, configKeyToFilePath, connect, createBackend, createConfigRepo, createSerializerChain, getAccountFields, getBackendMetadata, getExtension, hasBackend, incrementVersion, isBrowserEnv, isMtimeSidecar, legacyVersionPathFor, listBackendMetadata, listBackends, localPrimaryType, mergeAccountFields, migrateVersionSidecars, purgeMtimeSidecars, readVersion, registerBackend, registerFolderBackend, resolveLocalPrimary, sha256, unregisterBackend, verifyOrRepairVersion, versionPathFor, wrapZenFSFileSystem, writeVersion };
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,65 @@ import * as node_fs from 'node:fs';
|
|
|
4
4
|
import * as node_path from 'node:path';
|
|
5
5
|
import { SyncMapStore, SyncMapTransaction } from '@zenfs/core';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* zen-fs-config — mtime sidecar cleanup
|
|
9
|
+
*
|
|
10
|
+
* Backends that cannot store a precise mtime natively (RemoteStorage, Gitee,
|
|
11
|
+
* …) persist it in a `.mtime` sidecar next to each file
|
|
12
|
+
* (`data.json` → `.data.json.mtime`). A sidecar is backend-internal metadata:
|
|
13
|
+
* it belongs to the backend that produced it and must never be handed out by
|
|
14
|
+
* `readdir()`/`createSnapshot()`.
|
|
15
|
+
*
|
|
16
|
+
* Older builds did leak them, and once a sidecar lands in the local primary
|
|
17
|
+
* (IndexedDB on browser, Folder on Node) it stays there forever — zen-fs-sync
|
|
18
|
+
* deliberately skips `.mtime` paths on both sides, so nothing ever removes
|
|
19
|
+
* them, and every walk logs:
|
|
20
|
+
*
|
|
21
|
+
* [zen-fs-sync] mtime sidecar leaked from backend "local-idb" …
|
|
22
|
+
*
|
|
23
|
+
* This module walks the local primary and deletes those leaked files. It never
|
|
24
|
+
* touches replica (remote) backends: their sidecars are live metadata, and
|
|
25
|
+
* deleting them would cost network calls and lose mtime precision.
|
|
26
|
+
*/
|
|
27
|
+
/** Minimal async FS surface needed for the purge. */
|
|
28
|
+
interface PurgeableFS {
|
|
29
|
+
readdir(path: string): Promise<string[]>;
|
|
30
|
+
stat(path: string): Promise<{
|
|
31
|
+
mode?: number;
|
|
32
|
+
}>;
|
|
33
|
+
unlink(path: string): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
interface PurgeMtimeOptions {
|
|
36
|
+
/** Root to scan. Default: `/` (the whole local primary). */
|
|
37
|
+
root?: string;
|
|
38
|
+
/** Report what would be deleted without unlinking anything. */
|
|
39
|
+
dryRun?: boolean;
|
|
40
|
+
}
|
|
41
|
+
interface MtimePurgeResult {
|
|
42
|
+
/** Files visited during the walk (sidecars included). */
|
|
43
|
+
scanned: number;
|
|
44
|
+
/** Sidecars deleted — or, in dryRun, that would be deleted. */
|
|
45
|
+
removed: string[];
|
|
46
|
+
/** Sidecars that could not be deleted (unlink failed). */
|
|
47
|
+
failed: string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* True when a file name is an mtime sidecar.
|
|
51
|
+
*
|
|
52
|
+
* Uses the same rule as zen-fs-sync's walker (`path.endsWith('.mtime')`), so
|
|
53
|
+
* the pathological nested form (`.foo.mtime.mtime`) is covered too.
|
|
54
|
+
*/
|
|
55
|
+
declare function isMtimeSidecar(fileName: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Delete every `.mtime` sidecar stored in a local primary backend.
|
|
58
|
+
*
|
|
59
|
+
* Walks the whole tree from `options.root` — including dotfiles and `/.meta/`,
|
|
60
|
+
* because sidecars live next to their data file as `<name>.mtime` files.
|
|
61
|
+
* Unreadable entries are skipped; failures on individual files are collected
|
|
62
|
+
* in `failed` instead of aborting the walk.
|
|
63
|
+
*/
|
|
64
|
+
declare function purgeMtimeSidecars(fs: PurgeableFS, options?: PurgeMtimeOptions): Promise<MtimePurgeResult>;
|
|
65
|
+
|
|
7
66
|
/** A single backend in the topology. */
|
|
8
67
|
interface BackendDescriptor {
|
|
9
68
|
/** Unique identifier within this config repo (e.g., "local-idb"). */
|
|
@@ -159,6 +218,29 @@ interface ConfigRepoOptions {
|
|
|
159
218
|
* Default: 1800000 (30 minutes).
|
|
160
219
|
*/
|
|
161
220
|
syncPollIntervalMs?: number;
|
|
221
|
+
/**
|
|
222
|
+
* Delete leaked `.mtime` sidecars from the local primary on startup.
|
|
223
|
+
*
|
|
224
|
+
* Backends that keep a precise mtime out-of-band (RemoteStorage, Gitee…)
|
|
225
|
+
* write a `.mtime` file next to each data file. Older builds copied those
|
|
226
|
+
* backend-internal files into the local primary, where sync ignores them —
|
|
227
|
+
* so they linger forever and warn on every walk.
|
|
228
|
+
*
|
|
229
|
+
* Default: `true`. Pass `false` to skip the (local-only) startup sweep.
|
|
230
|
+
*/
|
|
231
|
+
purgeMtimeSidecars?: boolean;
|
|
232
|
+
/**
|
|
233
|
+
* Migrate legacy `.x.version` sidecars to the new `<name>.version` naming on
|
|
234
|
+
* startup (rename locally, delete the remote residual).
|
|
235
|
+
*
|
|
236
|
+
* Older builds stored version sidecars as hidden dotfiles (`.db.json.version`).
|
|
237
|
+
* They are now `<name>.version`. This one-time sweep renames existing legacy
|
|
238
|
+
* sidecars (preserving version history) and removes the old copy from every
|
|
239
|
+
* replica so sync does not pull it back.
|
|
240
|
+
*
|
|
241
|
+
* Default: `true`. Pass `false` to skip the startup sweep.
|
|
242
|
+
*/
|
|
243
|
+
migrateVersionSidecars?: boolean;
|
|
162
244
|
}
|
|
163
245
|
/** Type of sync group. */
|
|
164
246
|
type SyncGroupType = 'config-sync' | 'data-sync';
|
|
@@ -340,6 +422,17 @@ interface IConfigRepo {
|
|
|
340
422
|
* Called automatically by createConfigRepo() after setupSync().
|
|
341
423
|
*/
|
|
342
424
|
syncMetaToReplicas(): Promise<void>;
|
|
425
|
+
/**
|
|
426
|
+
* Delete leaked `.mtime` sidecars from the local primary backend.
|
|
427
|
+
*
|
|
428
|
+
* Runs automatically on `createConfigRepo()` unless
|
|
429
|
+
* `options.purgeMtimeSidecars === false`. Call it manually to clean a
|
|
430
|
+
* long-lived store, or with `{ dryRun: true }` to only list them.
|
|
431
|
+
*/
|
|
432
|
+
purgeMtimeSidecars(options?: {
|
|
433
|
+
root?: string;
|
|
434
|
+
dryRun?: boolean;
|
|
435
|
+
}): Promise<MtimePurgeResult>;
|
|
343
436
|
/**
|
|
344
437
|
* Create a data-sync group for this app.
|
|
345
438
|
* Each backend can optionally reference a config-sync backend's account
|
|
@@ -504,6 +597,71 @@ declare function getAccountFields(type: string): string[];
|
|
|
504
597
|
declare function mergeAccountFields(targetType: string, sourceOptions: Record<string, unknown>, targetOptions: Record<string, unknown>): Record<string, unknown>;
|
|
505
598
|
declare function wrapZenFSFileSystem(config: any): Promise<BackendInstance>;
|
|
506
599
|
|
|
600
|
+
/**
|
|
601
|
+
* zen-fs-config — Legacy Version Sidecar Migration
|
|
602
|
+
*
|
|
603
|
+
* Version sidecars used to be stored as hidden dotfiles:
|
|
604
|
+
*
|
|
605
|
+
* config file: /app-a/db.json
|
|
606
|
+
* version file: /app-a/.db.json.version (OLD — starts with `.`)
|
|
607
|
+
*
|
|
608
|
+
* They are now stored as `<name>.version` (no leading dot):
|
|
609
|
+
*
|
|
610
|
+
* version file: /app-a/db.json.version (NEW)
|
|
611
|
+
*
|
|
612
|
+
* This module migrates existing legacy sidecars to the new naming at startup,
|
|
613
|
+
* preserving version history (the file content is byte-identical after rename).
|
|
614
|
+
*
|
|
615
|
+
* Ambiguity safety: a legacy path `.x.version` could in theory belong to either
|
|
616
|
+
* a dotfile config `.x` (whose version is unchanged by the rename) or a
|
|
617
|
+
* non-dot config `x` (old naming). We resolve it by probing the directory:
|
|
618
|
+
* - `.x` exists → it's the dotfile config's version → NEW path == legacy → skip
|
|
619
|
+
* - `x` exists → rename legacy `.x.version` → `x.version`
|
|
620
|
+
* - neither → orphan (config gone) → delete
|
|
621
|
+
*
|
|
622
|
+
* We also delete the remote residual legacy path from every replica so the
|
|
623
|
+
* sync engine does not pull the old sidecar back (zen-fs-sync syncs `.version`
|
|
624
|
+
* files on both ends).
|
|
625
|
+
*/
|
|
626
|
+
/** Minimal async FS surface needed to read/rename/delete on the local primary. */
|
|
627
|
+
interface MigrateLocalFS {
|
|
628
|
+
readdir(path: string): Promise<string[]>;
|
|
629
|
+
stat(path: string): Promise<{
|
|
630
|
+
mode?: number;
|
|
631
|
+
}>;
|
|
632
|
+
readFile(path: string, encoding?: string): Promise<Uint8Array | string>;
|
|
633
|
+
writeFile(path: string, data: Uint8Array): Promise<void>;
|
|
634
|
+
unlink(path: string): Promise<void>;
|
|
635
|
+
}
|
|
636
|
+
/** Minimal async FS surface needed to delete remote residual legacy sidecars. */
|
|
637
|
+
interface MigrateReplicaFS {
|
|
638
|
+
unlink(path: string): Promise<void>;
|
|
639
|
+
}
|
|
640
|
+
interface VersionMigrationOptions {
|
|
641
|
+
/** Root to scan. Default: `/` (the whole local primary). */
|
|
642
|
+
root?: string;
|
|
643
|
+
/** Report what would change without touching anything. */
|
|
644
|
+
dryRun?: boolean;
|
|
645
|
+
/** Replica backends whose legacy sidecars should be deleted. */
|
|
646
|
+
replicas?: MigrateReplicaFS[];
|
|
647
|
+
}
|
|
648
|
+
interface VersionMigrationResult {
|
|
649
|
+
/** Local legacy sidecars renamed to the new naming. */
|
|
650
|
+
renamed: string[];
|
|
651
|
+
/** Local orphan legacy sidecars deleted (no owning config). */
|
|
652
|
+
deleted: string[];
|
|
653
|
+
/** Remote residual legacy sidecars deleted (best-effort). */
|
|
654
|
+
remoteDeleted: string[];
|
|
655
|
+
/** Paths that failed to migrate. */
|
|
656
|
+
failed: string[];
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Walk `fs` from `options.root` and migrate every legacy `.x.version` /
|
|
660
|
+
* `.x.version.mtime` sidecar to the new `<name>.version` / `<name>.version.mtime`
|
|
661
|
+
* naming. Best-effort: failures are collected in `failed`, never thrown.
|
|
662
|
+
*/
|
|
663
|
+
declare function migrateVersionSidecars(fs: MigrateLocalFS, options?: VersionMigrationOptions): Promise<VersionMigrationResult>;
|
|
664
|
+
|
|
507
665
|
/**
|
|
508
666
|
* zen-fs-config — ConfigRepo Implementation
|
|
509
667
|
*
|
|
@@ -593,6 +751,31 @@ declare class ConfigRepo implements IConfigRepo {
|
|
|
593
751
|
private safeExists;
|
|
594
752
|
/** Public wrapper for processTombstones — used by createConfigRepo. */
|
|
595
753
|
processTombstonesPublic(): Promise<void>;
|
|
754
|
+
/**
|
|
755
|
+
* Delete leaked `.mtime` sidecar files from the local primary backend
|
|
756
|
+
* (IndexedDB on browser, Folder on Node).
|
|
757
|
+
*
|
|
758
|
+
* Sidecars are produced by backends that keep a precise mtime out-of-band
|
|
759
|
+
* (RemoteStorage, Gitee…). Once one is copied into the local primary it is
|
|
760
|
+
* never removed: zen-fs-sync skips `.mtime` paths on both sides, so it is
|
|
761
|
+
* invisible to sync, and every walk warns about the leak.
|
|
762
|
+
*
|
|
763
|
+
* Only the local primary is scanned — replica sidecars are live metadata of
|
|
764
|
+
* the backend that owns them and must be left alone.
|
|
765
|
+
*
|
|
766
|
+
* @param options.root Scan only this subtree (default `/`).
|
|
767
|
+
* @param options.dryRun List the sidecars without deleting them.
|
|
768
|
+
*/
|
|
769
|
+
purgeMtimeSidecars(options?: PurgeMtimeOptions): Promise<MtimePurgeResult>;
|
|
770
|
+
/**
|
|
771
|
+
* Migrate legacy `.x.version` sidecars (old dotfile naming) to the new
|
|
772
|
+
* `<name>.version` naming. Renames local sidecars (preserving content/history)
|
|
773
|
+
* and deletes the residual legacy copy from every replica backend so sync
|
|
774
|
+
* does not pull it back. Best-effort: failures are logged, never thrown.
|
|
775
|
+
*
|
|
776
|
+
* Runs once at startup (see createConfigRepo). Safe to call again.
|
|
777
|
+
*/
|
|
778
|
+
migrateLegacyVersionSidecars(): Promise<VersionMigrationResult>;
|
|
596
779
|
/**
|
|
597
780
|
* Perform a full sync + dedup cycle without the watch snapshot cache.
|
|
598
781
|
* Used by createConfigRepo to pull remote-only files (like duplicate
|
|
@@ -779,24 +962,35 @@ declare function connect(appId: string, options?: ConnectOptions): Promise<Conne
|
|
|
779
962
|
/**
|
|
780
963
|
* zen-fs-config — Sidecar Version File Management
|
|
781
964
|
*
|
|
782
|
-
* Each config file has a companion
|
|
783
|
-
* detection and conflict resolution.
|
|
965
|
+
* Each config file has a companion `.version` sidecar file for version-based
|
|
966
|
+
* change detection and conflict resolution. The sidecar is named `<name>.version`
|
|
967
|
+
* (NOT `.name.version`) so it is not a hidden dotfile.
|
|
784
968
|
*
|
|
785
969
|
* Config file: /app-a/db.json
|
|
786
|
-
* Version file: /app-a
|
|
970
|
+
* Version file: /app-a/db.json.version
|
|
787
971
|
*/
|
|
788
972
|
|
|
789
973
|
/**
|
|
790
974
|
* Compute the sidecar version file path from a config file path.
|
|
791
975
|
*
|
|
792
|
-
* /app-a/db.json → /app-a
|
|
793
|
-
* /shared/flags.json → /shared
|
|
794
|
-
* /nodes/s1/env.json → /nodes/s1
|
|
976
|
+
* /app-a/db.json → /app-a/db.json.version
|
|
977
|
+
* /shared/flags.json → /shared/flags.json.version
|
|
978
|
+
* /nodes/s1/env.json → /nodes/s1/env.json.version
|
|
795
979
|
*
|
|
796
980
|
* Returns null for files that are already version sidecars (.version files),
|
|
797
|
-
* to prevent creating version-of-version files (e.g.
|
|
981
|
+
* to prevent creating version-of-version files (e.g. db.json.version.version).
|
|
798
982
|
*/
|
|
799
983
|
declare function versionPathFor(configFilePath: string): string | null;
|
|
984
|
+
/**
|
|
985
|
+
* Compute the *legacy* (pre-dotfile-removal) version sidecar path for a config
|
|
986
|
+
* file. Returns the old `.${name}.version` form for non-dot configs, or `null`
|
|
987
|
+
* for dotfile configs (whose version path is unchanged) and for version files
|
|
988
|
+
* themselves.
|
|
989
|
+
*
|
|
990
|
+
* Used as a read fallback so existing history in `.db.json.version` is still
|
|
991
|
+
* found after the naming scheme changed to `db.json.version`.
|
|
992
|
+
*/
|
|
993
|
+
declare function legacyVersionPathFor(configFilePath: string): string | null;
|
|
800
994
|
/**
|
|
801
995
|
* Compute SHA-256 hash of a Uint8Array.
|
|
802
996
|
* Returns "sha256:" prefix + hex digest.
|
|
@@ -826,4 +1020,4 @@ declare function incrementVersion(fs: SyncableFS, configFilePath: string, newCon
|
|
|
826
1020
|
*/
|
|
827
1021
|
declare function verifyOrRepairVersion(fs: SyncableFS, configFilePath: string, author: string): Promise<VersionMeta | null>;
|
|
828
1022
|
|
|
829
|
-
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 SyncGroupType, type TombstoneMeta, type VersionMeta, configKeyToFilePath, connect, createBackend, createConfigRepo, createSerializerChain, getAccountFields, getBackendMetadata, getExtension, hasBackend, incrementVersion, isBrowserEnv, listBackendMetadata, listBackends, localPrimaryType, mergeAccountFields, readVersion, registerBackend, registerFolderBackend, resolveLocalPrimary, sha256, unregisterBackend, verifyOrRepairVersion, versionPathFor, wrapZenFSFileSystem, writeVersion };
|
|
1023
|
+
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 MigrateLocalFS, type MigrateReplicaFS, type MtimePurgeResult, type PurgeMtimeOptions, type PurgeableFS, type SyncGroupType, type TombstoneMeta, type VersionMeta, type VersionMigrationOptions, type VersionMigrationResult, configKeyToFilePath, connect, createBackend, createConfigRepo, createSerializerChain, getAccountFields, getBackendMetadata, getExtension, hasBackend, incrementVersion, isBrowserEnv, isMtimeSidecar, legacyVersionPathFor, listBackendMetadata, listBackends, localPrimaryType, mergeAccountFields, migrateVersionSidecars, purgeMtimeSidecars, readVersion, registerBackend, registerFolderBackend, resolveLocalPrimary, sha256, unregisterBackend, verifyOrRepairVersion, versionPathFor, wrapZenFSFileSystem, writeVersion };
|