zen-fs-config 0.5.38 → 0.5.40
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.
|
@@ -4,7 +4,8 @@ var metadataRegistry = /* @__PURE__ */ new Map();
|
|
|
4
4
|
function registerBackend(type, factory, metadata) {
|
|
5
5
|
registry.set(type, factory);
|
|
6
6
|
if (metadata) {
|
|
7
|
-
metadataRegistry.
|
|
7
|
+
const existing = metadataRegistry.get(type);
|
|
8
|
+
metadataRegistry.set(type, { ...existing, ...metadata });
|
|
8
9
|
}
|
|
9
10
|
}
|
|
10
11
|
function unregisterBackend(type) {
|
|
@@ -121,6 +122,9 @@ async function wrapZenFSFileSystem(config) {
|
|
|
121
122
|
if (typeof isolatedFS.writeFileWithMtime === "function") {
|
|
122
123
|
backend.writeFileWithMtime = (path, data, mtimeMs) => isolatedFS.writeFileWithMtime(path, data, mtimeMs);
|
|
123
124
|
}
|
|
125
|
+
if (typeof isolatedFS.shouldSync === "function") {
|
|
126
|
+
backend.shouldSync = (...args) => isolatedFS.shouldSync(...args);
|
|
127
|
+
}
|
|
124
128
|
return backend;
|
|
125
129
|
}
|
|
126
130
|
var inMemoryCounter = 0;
|
|
@@ -184,7 +188,8 @@ var OPTIONAL_BACKEND_GLOBALS = {
|
|
|
184
188
|
// third-party Gitee widget) would shadow the real backend and break mounting
|
|
185
189
|
// with "Invalid mount configuration".
|
|
186
190
|
"zen-fs-gitee": ["ZenFSGitee"],
|
|
187
|
-
"zen-fs-remotestoragejs": ["ZenFSRemoteStorage", "RemoteStorageFileSystem"]
|
|
191
|
+
"zen-fs-remotestoragejs": ["ZenFSRemoteStorage", "RemoteStorageFileSystem"],
|
|
192
|
+
"zen-fs-webdav": ["ZenFSWebDAV"]
|
|
188
193
|
};
|
|
189
194
|
async function importOptionalBackend(pkg) {
|
|
190
195
|
const candidates = OPTIONAL_BACKEND_GLOBALS[pkg];
|
|
@@ -332,6 +337,53 @@ registerBackend("RemoteStorage", async (options) => {
|
|
|
332
337
|
// connections to the same endpoint with different tuning are deduplicated.
|
|
333
338
|
identityFields: ["href", "token", "basePath"]
|
|
334
339
|
});
|
|
340
|
+
registerBackend("WebDAV", async (options) => {
|
|
341
|
+
const _dav = await importOptionalBackend("zen-fs-webdav");
|
|
342
|
+
const SyncableWebDAVFS = _dav?.SyncableWebDAVFS ?? _dav?.default?.SyncableWebDAVFS ?? globalThis.ZenFSWebDAV?.SyncableWebDAVFS;
|
|
343
|
+
if (typeof SyncableWebDAVFS !== "function") {
|
|
344
|
+
throw new Error(
|
|
345
|
+
`Backend package "zen-fs-webdav" loaded, but its SyncableWebDAVFS export is not a constructor (typeof=${typeof SyncableWebDAVFS}). Run: npm install zen-fs-webdav, or load its UMD bundle via <script src=".../zen-fs-webdav.global.js"> so it exposes the global "ZenFSWebDAV".`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
const instance = new SyncableWebDAVFS({
|
|
349
|
+
baseUrl: options.baseUrl,
|
|
350
|
+
username: options.username,
|
|
351
|
+
password: options.password,
|
|
352
|
+
token: options.token,
|
|
353
|
+
headers: options.headers,
|
|
354
|
+
timeout: options.timeout,
|
|
355
|
+
preciseMtime: true
|
|
356
|
+
});
|
|
357
|
+
if (options.purgeLegacyMtime === true) {
|
|
358
|
+
try {
|
|
359
|
+
await instance.purgeLegacyMtimeSidecars({ root: options.basePath ?? "/" });
|
|
360
|
+
} catch {
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return instance;
|
|
364
|
+
}, {
|
|
365
|
+
type: "WebDAV",
|
|
366
|
+
label: "WebDAV",
|
|
367
|
+
icon: "\u2601\uFE0F",
|
|
368
|
+
fields: [
|
|
369
|
+
{ key: "baseUrl", label: "Base URL", type: "text", placeholder: "https://dav.example.com/remote.php/dav/files/user/", required: true },
|
|
370
|
+
{ key: "username", label: "Username", type: "text", placeholder: "admin" },
|
|
371
|
+
{ key: "password", label: "Password", type: "password" },
|
|
372
|
+
{ key: "token", label: "Token", type: "password", placeholder: "Bearer token (optional)" },
|
|
373
|
+
{ key: "basePath", label: "Base Path", type: "text", placeholder: "/zen-fs-config/" },
|
|
374
|
+
{ key: "purgeLegacyMtime", label: "Purge legacy .mtime sidecars", type: "select", options: [
|
|
375
|
+
{ value: "false", label: "no (default)" },
|
|
376
|
+
{ value: "true", label: "yes (one-time migration)" }
|
|
377
|
+
] }
|
|
378
|
+
],
|
|
379
|
+
defaultOptions: { baseUrl: "", username: "", password: "", token: "", basePath: "/", purgeLegacyMtime: "false" },
|
|
380
|
+
// accountFields: credentials live on the endpoint, so a data-sync group can
|
|
381
|
+
// reuse them from an already-connected config-sync WebDAV backend.
|
|
382
|
+
accountFields: ["username", "password", "token"],
|
|
383
|
+
// identity = the WebDAV endpoint. Credentials are auth, not location, so two
|
|
384
|
+
// connections to the same URL are deduplicated as one replica.
|
|
385
|
+
identityFields: ["baseUrl"]
|
|
386
|
+
});
|
|
335
387
|
|
|
336
388
|
export {
|
|
337
389
|
registerBackend,
|
package/dist/index.js
CHANGED
|
@@ -47,7 +47,8 @@ __export(backend_registry_exports, {
|
|
|
47
47
|
function registerBackend(type, factory, metadata) {
|
|
48
48
|
registry.set(type, factory);
|
|
49
49
|
if (metadata) {
|
|
50
|
-
metadataRegistry.
|
|
50
|
+
const existing = metadataRegistry.get(type);
|
|
51
|
+
metadataRegistry.set(type, { ...existing, ...metadata });
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
function unregisterBackend(type) {
|
|
@@ -164,6 +165,9 @@ async function wrapZenFSFileSystem(config) {
|
|
|
164
165
|
if (typeof isolatedFS.writeFileWithMtime === "function") {
|
|
165
166
|
backend.writeFileWithMtime = (path, data, mtimeMs) => isolatedFS.writeFileWithMtime(path, data, mtimeMs);
|
|
166
167
|
}
|
|
168
|
+
if (typeof isolatedFS.shouldSync === "function") {
|
|
169
|
+
backend.shouldSync = (...args) => isolatedFS.shouldSync(...args);
|
|
170
|
+
}
|
|
167
171
|
return backend;
|
|
168
172
|
}
|
|
169
173
|
function _cacheGet(key) {
|
|
@@ -259,7 +263,8 @@ var init_backend_registry = __esm({
|
|
|
259
263
|
// third-party Gitee widget) would shadow the real backend and break mounting
|
|
260
264
|
// with "Invalid mount configuration".
|
|
261
265
|
"zen-fs-gitee": ["ZenFSGitee"],
|
|
262
|
-
"zen-fs-remotestoragejs": ["ZenFSRemoteStorage", "RemoteStorageFileSystem"]
|
|
266
|
+
"zen-fs-remotestoragejs": ["ZenFSRemoteStorage", "RemoteStorageFileSystem"],
|
|
267
|
+
"zen-fs-webdav": ["ZenFSWebDAV"]
|
|
263
268
|
};
|
|
264
269
|
registerBackend("GitHub", async (options) => {
|
|
265
270
|
const _gh = await importOptionalBackend("zen-fs-github");
|
|
@@ -381,6 +386,53 @@ var init_backend_registry = __esm({
|
|
|
381
386
|
// connections to the same endpoint with different tuning are deduplicated.
|
|
382
387
|
identityFields: ["href", "token", "basePath"]
|
|
383
388
|
});
|
|
389
|
+
registerBackend("WebDAV", async (options) => {
|
|
390
|
+
const _dav = await importOptionalBackend("zen-fs-webdav");
|
|
391
|
+
const SyncableWebDAVFS = _dav?.SyncableWebDAVFS ?? _dav?.default?.SyncableWebDAVFS ?? globalThis.ZenFSWebDAV?.SyncableWebDAVFS;
|
|
392
|
+
if (typeof SyncableWebDAVFS !== "function") {
|
|
393
|
+
throw new Error(
|
|
394
|
+
`Backend package "zen-fs-webdav" loaded, but its SyncableWebDAVFS export is not a constructor (typeof=${typeof SyncableWebDAVFS}). Run: npm install zen-fs-webdav, or load its UMD bundle via <script src=".../zen-fs-webdav.global.js"> so it exposes the global "ZenFSWebDAV".`
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
const instance = new SyncableWebDAVFS({
|
|
398
|
+
baseUrl: options.baseUrl,
|
|
399
|
+
username: options.username,
|
|
400
|
+
password: options.password,
|
|
401
|
+
token: options.token,
|
|
402
|
+
headers: options.headers,
|
|
403
|
+
timeout: options.timeout,
|
|
404
|
+
preciseMtime: true
|
|
405
|
+
});
|
|
406
|
+
if (options.purgeLegacyMtime === true) {
|
|
407
|
+
try {
|
|
408
|
+
await instance.purgeLegacyMtimeSidecars({ root: options.basePath ?? "/" });
|
|
409
|
+
} catch {
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return instance;
|
|
413
|
+
}, {
|
|
414
|
+
type: "WebDAV",
|
|
415
|
+
label: "WebDAV",
|
|
416
|
+
icon: "\u2601\uFE0F",
|
|
417
|
+
fields: [
|
|
418
|
+
{ key: "baseUrl", label: "Base URL", type: "text", placeholder: "https://dav.example.com/remote.php/dav/files/user/", required: true },
|
|
419
|
+
{ key: "username", label: "Username", type: "text", placeholder: "admin" },
|
|
420
|
+
{ key: "password", label: "Password", type: "password" },
|
|
421
|
+
{ key: "token", label: "Token", type: "password", placeholder: "Bearer token (optional)" },
|
|
422
|
+
{ key: "basePath", label: "Base Path", type: "text", placeholder: "/zen-fs-config/" },
|
|
423
|
+
{ key: "purgeLegacyMtime", label: "Purge legacy .mtime sidecars", type: "select", options: [
|
|
424
|
+
{ value: "false", label: "no (default)" },
|
|
425
|
+
{ value: "true", label: "yes (one-time migration)" }
|
|
426
|
+
] }
|
|
427
|
+
],
|
|
428
|
+
defaultOptions: { baseUrl: "", username: "", password: "", token: "", basePath: "/", purgeLegacyMtime: "false" },
|
|
429
|
+
// accountFields: credentials live on the endpoint, so a data-sync group can
|
|
430
|
+
// reuse them from an already-connected config-sync WebDAV backend.
|
|
431
|
+
accountFields: ["username", "password", "token"],
|
|
432
|
+
// identity = the WebDAV endpoint. Credentials are auth, not location, so two
|
|
433
|
+
// connections to the same URL are deduplicated as one replica.
|
|
434
|
+
identityFields: ["baseUrl"]
|
|
435
|
+
});
|
|
384
436
|
}
|
|
385
437
|
});
|
|
386
438
|
|
|
@@ -1360,17 +1412,12 @@ function sortKeysDeep(obj) {
|
|
|
1360
1412
|
return sorted;
|
|
1361
1413
|
}
|
|
1362
1414
|
function identityOptions(type, options) {
|
|
1363
|
-
const
|
|
1364
|
-
|
|
1365
|
-
if (!fields || fields.length === 0) {
|
|
1366
|
-
log2.warn(`[DEDUP-DIAG] identityOptions(${type}): NO identityFields -> falling back to FULL options (dedup will be key-sensitive to tuning fields)`);
|
|
1367
|
-
return options ?? {};
|
|
1368
|
-
}
|
|
1415
|
+
const fields = getBackendMetadata(type)?.identityFields;
|
|
1416
|
+
if (!fields || fields.length === 0) return options ?? {};
|
|
1369
1417
|
const subset = {};
|
|
1370
1418
|
for (const f of fields) {
|
|
1371
1419
|
if (options && options[f] !== void 0) subset[f] = options[f];
|
|
1372
1420
|
}
|
|
1373
|
-
log2.log(`[DEDUP-DIAG] identityOptions(${type}): identityFields=${JSON.stringify(fields)} identitySubset=${JSON.stringify(subset)}`);
|
|
1374
1421
|
return subset;
|
|
1375
1422
|
}
|
|
1376
1423
|
function backendDedupKey(desc) {
|
|
@@ -2409,22 +2456,18 @@ var ConfigRepo = class {
|
|
|
2409
2456
|
const duplicates = [];
|
|
2410
2457
|
for (const item of items) {
|
|
2411
2458
|
const key = backendDedupKey(item.desc);
|
|
2412
|
-
log2.log(`[DEDUP-DIAG] item id=${item.desc.id} type=${item.desc.type} dedupKey=${key} mtime=${item.mtime} rawOptions=${JSON.stringify(item.desc.options)}`);
|
|
2413
2459
|
const existing = seen.get(key);
|
|
2414
2460
|
if (existing) {
|
|
2415
2461
|
if (item.mtime < existing.mtime) {
|
|
2416
|
-
log2.log(`[DEDUP-DIAG] DUPLICATE: new id=${item.desc.id} (older) keeps key, dropping existing=${existing.desc.id}`);
|
|
2417
2462
|
duplicates.push(existing.desc.id);
|
|
2418
2463
|
seen.set(key, item);
|
|
2419
2464
|
} else {
|
|
2420
|
-
log2.log(`[DEDUP-DIAG] DUPLICATE: new id=${item.desc.id} dropped, kept=${existing.desc.id}`);
|
|
2421
2465
|
duplicates.push(item.desc.id);
|
|
2422
2466
|
}
|
|
2423
2467
|
} else {
|
|
2424
2468
|
seen.set(key, item);
|
|
2425
2469
|
}
|
|
2426
2470
|
}
|
|
2427
|
-
log2.log(`[DEDUP-DIAG] dedup summary: keptKeys=${[...seen.keys()].length} duplicates=${JSON.stringify(duplicates)}`);
|
|
2428
2471
|
if (duplicates.length > 0) {
|
|
2429
2472
|
log2.log(
|
|
2430
2473
|
`[ConfigRepo] readAllBackendDescriptors: removing ${duplicates.length} duplicate(s): ${duplicates.join(", ")}`
|
|
@@ -2434,13 +2477,10 @@ var ConfigRepo = class {
|
|
|
2434
2477
|
for (const [replicaId, replica] of this.replicaBackends) {
|
|
2435
2478
|
try {
|
|
2436
2479
|
await replica.instance.unlink(descPath);
|
|
2437
|
-
|
|
2438
|
-
} catch (e) {
|
|
2439
|
-
log2.log(`[DEDUP-DIAG] remove dup ${dupId} from replica ${replicaId} failed/skipped: ${String(e)}`);
|
|
2480
|
+
} catch {
|
|
2440
2481
|
}
|
|
2441
2482
|
await this.unlinkVersionSidecar(replica.instance, descPath);
|
|
2442
2483
|
}
|
|
2443
|
-
log2.log(`[DEDUP-DIAG] replicaBackends available during removal: ${[...this.replicaBackends.keys()].join(",") || "(none)"}`);
|
|
2444
2484
|
try {
|
|
2445
2485
|
await this.deleteFile(descPath);
|
|
2446
2486
|
} catch {
|
|
@@ -2449,8 +2489,7 @@ var ConfigRepo = class {
|
|
|
2449
2489
|
}
|
|
2450
2490
|
}
|
|
2451
2491
|
return Array.from(seen.values()).map((i) => i.desc);
|
|
2452
|
-
} catch
|
|
2453
|
-
log2.error(`[DEDUP-DIAG] readAllBackendDescriptors threw, returning []:`, e);
|
|
2492
|
+
} catch {
|
|
2454
2493
|
return [];
|
|
2455
2494
|
}
|
|
2456
2495
|
}
|
|
@@ -3054,7 +3093,6 @@ async function createConfigRepo(appId, options = {}) {
|
|
|
3054
3093
|
log2.log(`[createConfigRepo] Migration complete`);
|
|
3055
3094
|
}
|
|
3056
3095
|
let allBackends = await tempRepo.readAllBackendDescriptors();
|
|
3057
|
-
log2.log(`[DEDUP-DIAG] createConfigRepo after readAllBackendDescriptors: allBackends=${allBackends.map((b) => b.id).join(",")}`);
|
|
3058
3096
|
if (options.backendInfo) {
|
|
3059
3097
|
const replicaId = options.primaryBackendId || `${options.backendInfo.type}-replica`;
|
|
3060
3098
|
const hasReplica = allBackends.some((b) => b.id === replicaId);
|
package/dist/index.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
registerBackend,
|
|
10
10
|
unregisterBackend,
|
|
11
11
|
wrapZenFSFileSystem
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-NSEXSKVW.mjs";
|
|
13
13
|
|
|
14
14
|
// src/config-repo.ts
|
|
15
15
|
import {
|
|
@@ -351,7 +351,7 @@ function registerFolderBackend() {
|
|
|
351
351
|
const pathMod = await import("path");
|
|
352
352
|
const dir = options.path;
|
|
353
353
|
const store = new FolderStore({ path: dir, label: options.label, fs, pathMod });
|
|
354
|
-
return (await import("./backend-registry-
|
|
354
|
+
return (await import("./backend-registry-ZFTXH537.mjs")).wrapZenFSFileSystem({ backend: FolderBackend, path: dir, label: options.label });
|
|
355
355
|
},
|
|
356
356
|
{
|
|
357
357
|
type: "Folder",
|
|
@@ -905,17 +905,12 @@ function sortKeysDeep(obj) {
|
|
|
905
905
|
return sorted;
|
|
906
906
|
}
|
|
907
907
|
function identityOptions(type, options) {
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
if (!fields || fields.length === 0) {
|
|
911
|
-
log2.warn(`[DEDUP-DIAG] identityOptions(${type}): NO identityFields -> falling back to FULL options (dedup will be key-sensitive to tuning fields)`);
|
|
912
|
-
return options ?? {};
|
|
913
|
-
}
|
|
908
|
+
const fields = getBackendMetadata(type)?.identityFields;
|
|
909
|
+
if (!fields || fields.length === 0) return options ?? {};
|
|
914
910
|
const subset = {};
|
|
915
911
|
for (const f of fields) {
|
|
916
912
|
if (options && options[f] !== void 0) subset[f] = options[f];
|
|
917
913
|
}
|
|
918
|
-
log2.log(`[DEDUP-DIAG] identityOptions(${type}): identityFields=${JSON.stringify(fields)} identitySubset=${JSON.stringify(subset)}`);
|
|
919
914
|
return subset;
|
|
920
915
|
}
|
|
921
916
|
function backendDedupKey(desc) {
|
|
@@ -1954,22 +1949,18 @@ var ConfigRepo = class {
|
|
|
1954
1949
|
const duplicates = [];
|
|
1955
1950
|
for (const item of items) {
|
|
1956
1951
|
const key = backendDedupKey(item.desc);
|
|
1957
|
-
log2.log(`[DEDUP-DIAG] item id=${item.desc.id} type=${item.desc.type} dedupKey=${key} mtime=${item.mtime} rawOptions=${JSON.stringify(item.desc.options)}`);
|
|
1958
1952
|
const existing = seen.get(key);
|
|
1959
1953
|
if (existing) {
|
|
1960
1954
|
if (item.mtime < existing.mtime) {
|
|
1961
|
-
log2.log(`[DEDUP-DIAG] DUPLICATE: new id=${item.desc.id} (older) keeps key, dropping existing=${existing.desc.id}`);
|
|
1962
1955
|
duplicates.push(existing.desc.id);
|
|
1963
1956
|
seen.set(key, item);
|
|
1964
1957
|
} else {
|
|
1965
|
-
log2.log(`[DEDUP-DIAG] DUPLICATE: new id=${item.desc.id} dropped, kept=${existing.desc.id}`);
|
|
1966
1958
|
duplicates.push(item.desc.id);
|
|
1967
1959
|
}
|
|
1968
1960
|
} else {
|
|
1969
1961
|
seen.set(key, item);
|
|
1970
1962
|
}
|
|
1971
1963
|
}
|
|
1972
|
-
log2.log(`[DEDUP-DIAG] dedup summary: keptKeys=${[...seen.keys()].length} duplicates=${JSON.stringify(duplicates)}`);
|
|
1973
1964
|
if (duplicates.length > 0) {
|
|
1974
1965
|
log2.log(
|
|
1975
1966
|
`[ConfigRepo] readAllBackendDescriptors: removing ${duplicates.length} duplicate(s): ${duplicates.join(", ")}`
|
|
@@ -1979,13 +1970,10 @@ var ConfigRepo = class {
|
|
|
1979
1970
|
for (const [replicaId, replica] of this.replicaBackends) {
|
|
1980
1971
|
try {
|
|
1981
1972
|
await replica.instance.unlink(descPath);
|
|
1982
|
-
|
|
1983
|
-
} catch (e) {
|
|
1984
|
-
log2.log(`[DEDUP-DIAG] remove dup ${dupId} from replica ${replicaId} failed/skipped: ${String(e)}`);
|
|
1973
|
+
} catch {
|
|
1985
1974
|
}
|
|
1986
1975
|
await this.unlinkVersionSidecar(replica.instance, descPath);
|
|
1987
1976
|
}
|
|
1988
|
-
log2.log(`[DEDUP-DIAG] replicaBackends available during removal: ${[...this.replicaBackends.keys()].join(",") || "(none)"}`);
|
|
1989
1977
|
try {
|
|
1990
1978
|
await this.deleteFile(descPath);
|
|
1991
1979
|
} catch {
|
|
@@ -1994,8 +1982,7 @@ var ConfigRepo = class {
|
|
|
1994
1982
|
}
|
|
1995
1983
|
}
|
|
1996
1984
|
return Array.from(seen.values()).map((i) => i.desc);
|
|
1997
|
-
} catch
|
|
1998
|
-
log2.error(`[DEDUP-DIAG] readAllBackendDescriptors threw, returning []:`, e);
|
|
1985
|
+
} catch {
|
|
1999
1986
|
return [];
|
|
2000
1987
|
}
|
|
2001
1988
|
}
|
|
@@ -2599,7 +2586,6 @@ async function createConfigRepo(appId, options = {}) {
|
|
|
2599
2586
|
log2.log(`[createConfigRepo] Migration complete`);
|
|
2600
2587
|
}
|
|
2601
2588
|
let allBackends = await tempRepo.readAllBackendDescriptors();
|
|
2602
|
-
log2.log(`[DEDUP-DIAG] createConfigRepo after readAllBackendDescriptors: allBackends=${allBackends.map((b) => b.id).join(",")}`);
|
|
2603
2589
|
if (options.backendInfo) {
|
|
2604
2590
|
const replicaId = options.primaryBackendId || `${options.backendInfo.type}-replica`;
|
|
2605
2591
|
const hasReplica = allBackends.some((b) => b.id === replicaId);
|
package/dist/zen-fs-config.js
CHANGED
|
@@ -24360,7 +24360,8 @@ var ZenFSConfig = (() => {
|
|
|
24360
24360
|
function registerBackend(type, factory, metadata) {
|
|
24361
24361
|
registry.set(type, factory);
|
|
24362
24362
|
if (metadata) {
|
|
24363
|
-
metadataRegistry.
|
|
24363
|
+
const existing = metadataRegistry.get(type);
|
|
24364
|
+
metadataRegistry.set(type, { ...existing, ...metadata });
|
|
24364
24365
|
}
|
|
24365
24366
|
}
|
|
24366
24367
|
function unregisterBackend(type) {
|
|
@@ -24477,6 +24478,9 @@ var ZenFSConfig = (() => {
|
|
|
24477
24478
|
if (typeof isolatedFS.writeFileWithMtime === "function") {
|
|
24478
24479
|
backend.writeFileWithMtime = (path, data, mtimeMs) => isolatedFS.writeFileWithMtime(path, data, mtimeMs);
|
|
24479
24480
|
}
|
|
24481
|
+
if (typeof isolatedFS.shouldSync === "function") {
|
|
24482
|
+
backend.shouldSync = (...args) => isolatedFS.shouldSync(...args);
|
|
24483
|
+
}
|
|
24480
24484
|
return backend;
|
|
24481
24485
|
}
|
|
24482
24486
|
function _cacheGet(key) {
|
|
@@ -24572,7 +24576,8 @@ var ZenFSConfig = (() => {
|
|
|
24572
24576
|
// third-party Gitee widget) would shadow the real backend and break mounting
|
|
24573
24577
|
// with "Invalid mount configuration".
|
|
24574
24578
|
"zen-fs-gitee": ["ZenFSGitee"],
|
|
24575
|
-
"zen-fs-remotestoragejs": ["ZenFSRemoteStorage", "RemoteStorageFileSystem"]
|
|
24579
|
+
"zen-fs-remotestoragejs": ["ZenFSRemoteStorage", "RemoteStorageFileSystem"],
|
|
24580
|
+
"zen-fs-webdav": ["ZenFSWebDAV"]
|
|
24576
24581
|
};
|
|
24577
24582
|
registerBackend("GitHub", async (options) => {
|
|
24578
24583
|
const _gh = await importOptionalBackend("zen-fs-github");
|
|
@@ -24694,6 +24699,53 @@ var ZenFSConfig = (() => {
|
|
|
24694
24699
|
// connections to the same endpoint with different tuning are deduplicated.
|
|
24695
24700
|
identityFields: ["href", "token", "basePath"]
|
|
24696
24701
|
});
|
|
24702
|
+
registerBackend("WebDAV", async (options) => {
|
|
24703
|
+
const _dav = await importOptionalBackend("zen-fs-webdav");
|
|
24704
|
+
const SyncableWebDAVFS = _dav?.SyncableWebDAVFS ?? _dav?.default?.SyncableWebDAVFS ?? globalThis.ZenFSWebDAV?.SyncableWebDAVFS;
|
|
24705
|
+
if (typeof SyncableWebDAVFS !== "function") {
|
|
24706
|
+
throw new Error(
|
|
24707
|
+
`Backend package "zen-fs-webdav" loaded, but its SyncableWebDAVFS export is not a constructor (typeof=${typeof SyncableWebDAVFS}). Run: npm install zen-fs-webdav, or load its UMD bundle via <script src=".../zen-fs-webdav.global.js"> so it exposes the global "ZenFSWebDAV".`
|
|
24708
|
+
);
|
|
24709
|
+
}
|
|
24710
|
+
const instance = new SyncableWebDAVFS({
|
|
24711
|
+
baseUrl: options.baseUrl,
|
|
24712
|
+
username: options.username,
|
|
24713
|
+
password: options.password,
|
|
24714
|
+
token: options.token,
|
|
24715
|
+
headers: options.headers,
|
|
24716
|
+
timeout: options.timeout,
|
|
24717
|
+
preciseMtime: true
|
|
24718
|
+
});
|
|
24719
|
+
if (options.purgeLegacyMtime === true) {
|
|
24720
|
+
try {
|
|
24721
|
+
await instance.purgeLegacyMtimeSidecars({ root: options.basePath ?? "/" });
|
|
24722
|
+
} catch {
|
|
24723
|
+
}
|
|
24724
|
+
}
|
|
24725
|
+
return instance;
|
|
24726
|
+
}, {
|
|
24727
|
+
type: "WebDAV",
|
|
24728
|
+
label: "WebDAV",
|
|
24729
|
+
icon: "\u2601\uFE0F",
|
|
24730
|
+
fields: [
|
|
24731
|
+
{ key: "baseUrl", label: "Base URL", type: "text", placeholder: "https://dav.example.com/remote.php/dav/files/user/", required: true },
|
|
24732
|
+
{ key: "username", label: "Username", type: "text", placeholder: "admin" },
|
|
24733
|
+
{ key: "password", label: "Password", type: "password" },
|
|
24734
|
+
{ key: "token", label: "Token", type: "password", placeholder: "Bearer token (optional)" },
|
|
24735
|
+
{ key: "basePath", label: "Base Path", type: "text", placeholder: "/zen-fs-config/" },
|
|
24736
|
+
{ key: "purgeLegacyMtime", label: "Purge legacy .mtime sidecars", type: "select", options: [
|
|
24737
|
+
{ value: "false", label: "no (default)" },
|
|
24738
|
+
{ value: "true", label: "yes (one-time migration)" }
|
|
24739
|
+
] }
|
|
24740
|
+
],
|
|
24741
|
+
defaultOptions: { baseUrl: "", username: "", password: "", token: "", basePath: "/", purgeLegacyMtime: "false" },
|
|
24742
|
+
// accountFields: credentials live on the endpoint, so a data-sync group can
|
|
24743
|
+
// reuse them from an already-connected config-sync WebDAV backend.
|
|
24744
|
+
accountFields: ["username", "password", "token"],
|
|
24745
|
+
// identity = the WebDAV endpoint. Credentials are auth, not location, so two
|
|
24746
|
+
// connections to the same URL are deduplicated as one replica.
|
|
24747
|
+
identityFields: ["baseUrl"]
|
|
24748
|
+
});
|
|
24697
24749
|
}
|
|
24698
24750
|
});
|
|
24699
24751
|
|
|
@@ -27293,17 +27345,12 @@ var ZenFSConfig = (() => {
|
|
|
27293
27345
|
return sorted;
|
|
27294
27346
|
}
|
|
27295
27347
|
function identityOptions(type, options) {
|
|
27296
|
-
const
|
|
27297
|
-
|
|
27298
|
-
if (!fields || fields.length === 0) {
|
|
27299
|
-
log6.warn(`[DEDUP-DIAG] identityOptions(${type}): NO identityFields -> falling back to FULL options (dedup will be key-sensitive to tuning fields)`);
|
|
27300
|
-
return options ?? {};
|
|
27301
|
-
}
|
|
27348
|
+
const fields = getBackendMetadata(type)?.identityFields;
|
|
27349
|
+
if (!fields || fields.length === 0) return options ?? {};
|
|
27302
27350
|
const subset = {};
|
|
27303
27351
|
for (const f of fields) {
|
|
27304
27352
|
if (options && options[f] !== void 0) subset[f] = options[f];
|
|
27305
27353
|
}
|
|
27306
|
-
log6.log(`[DEDUP-DIAG] identityOptions(${type}): identityFields=${JSON.stringify(fields)} identitySubset=${JSON.stringify(subset)}`);
|
|
27307
27354
|
return subset;
|
|
27308
27355
|
}
|
|
27309
27356
|
function backendDedupKey(desc) {
|
|
@@ -28342,22 +28389,18 @@ var ZenFSConfig = (() => {
|
|
|
28342
28389
|
const duplicates = [];
|
|
28343
28390
|
for (const item of items) {
|
|
28344
28391
|
const key = backendDedupKey(item.desc);
|
|
28345
|
-
log6.log(`[DEDUP-DIAG] item id=${item.desc.id} type=${item.desc.type} dedupKey=${key} mtime=${item.mtime} rawOptions=${JSON.stringify(item.desc.options)}`);
|
|
28346
28392
|
const existing = seen.get(key);
|
|
28347
28393
|
if (existing) {
|
|
28348
28394
|
if (item.mtime < existing.mtime) {
|
|
28349
|
-
log6.log(`[DEDUP-DIAG] DUPLICATE: new id=${item.desc.id} (older) keeps key, dropping existing=${existing.desc.id}`);
|
|
28350
28395
|
duplicates.push(existing.desc.id);
|
|
28351
28396
|
seen.set(key, item);
|
|
28352
28397
|
} else {
|
|
28353
|
-
log6.log(`[DEDUP-DIAG] DUPLICATE: new id=${item.desc.id} dropped, kept=${existing.desc.id}`);
|
|
28354
28398
|
duplicates.push(item.desc.id);
|
|
28355
28399
|
}
|
|
28356
28400
|
} else {
|
|
28357
28401
|
seen.set(key, item);
|
|
28358
28402
|
}
|
|
28359
28403
|
}
|
|
28360
|
-
log6.log(`[DEDUP-DIAG] dedup summary: keptKeys=${[...seen.keys()].length} duplicates=${JSON.stringify(duplicates)}`);
|
|
28361
28404
|
if (duplicates.length > 0) {
|
|
28362
28405
|
log6.log(
|
|
28363
28406
|
`[ConfigRepo] readAllBackendDescriptors: removing ${duplicates.length} duplicate(s): ${duplicates.join(", ")}`
|
|
@@ -28367,13 +28410,10 @@ var ZenFSConfig = (() => {
|
|
|
28367
28410
|
for (const [replicaId, replica] of this.replicaBackends) {
|
|
28368
28411
|
try {
|
|
28369
28412
|
await replica.instance.unlink(descPath);
|
|
28370
|
-
|
|
28371
|
-
} catch (e) {
|
|
28372
|
-
log6.log(`[DEDUP-DIAG] remove dup ${dupId} from replica ${replicaId} failed/skipped: ${String(e)}`);
|
|
28413
|
+
} catch {
|
|
28373
28414
|
}
|
|
28374
28415
|
await this.unlinkVersionSidecar(replica.instance, descPath);
|
|
28375
28416
|
}
|
|
28376
|
-
log6.log(`[DEDUP-DIAG] replicaBackends available during removal: ${[...this.replicaBackends.keys()].join(",") || "(none)"}`);
|
|
28377
28417
|
try {
|
|
28378
28418
|
await this.deleteFile(descPath);
|
|
28379
28419
|
} catch {
|
|
@@ -28382,8 +28422,7 @@ var ZenFSConfig = (() => {
|
|
|
28382
28422
|
}
|
|
28383
28423
|
}
|
|
28384
28424
|
return Array.from(seen.values()).map((i) => i.desc);
|
|
28385
|
-
} catch
|
|
28386
|
-
log6.error(`[DEDUP-DIAG] readAllBackendDescriptors threw, returning []:`, e);
|
|
28425
|
+
} catch {
|
|
28387
28426
|
return [];
|
|
28388
28427
|
}
|
|
28389
28428
|
}
|
|
@@ -28987,7 +29026,6 @@ var ZenFSConfig = (() => {
|
|
|
28987
29026
|
log6.log(`[createConfigRepo] Migration complete`);
|
|
28988
29027
|
}
|
|
28989
29028
|
let allBackends = await tempRepo.readAllBackendDescriptors();
|
|
28990
|
-
log6.log(`[DEDUP-DIAG] createConfigRepo after readAllBackendDescriptors: allBackends=${allBackends.map((b) => b.id).join(",")}`);
|
|
28991
29029
|
if (options.backendInfo) {
|
|
28992
29030
|
const replicaId = options.primaryBackendId || `${options.backendInfo.type}-replica`;
|
|
28993
29031
|
const hasReplica = allBackends.some((b) => b.id === replicaId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zen-fs-config",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.40",
|
|
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",
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
"zen-fs-sync": ">=0.4.7",
|
|
53
53
|
"zen-fs-github": "*",
|
|
54
54
|
"zen-fs-gitee": "*",
|
|
55
|
-
"zen-fs-remotestoragejs": "*"
|
|
55
|
+
"zen-fs-remotestoragejs": "*",
|
|
56
|
+
"zen-fs-webdav": "*"
|
|
56
57
|
},
|
|
57
58
|
"dependencies": {
|
|
58
59
|
"@richard432/localstorage-logger": "^0.1.0"
|
|
@@ -72,6 +73,9 @@
|
|
|
72
73
|
},
|
|
73
74
|
"zen-fs-remotestoragejs": {
|
|
74
75
|
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"zen-fs-webdav": {
|
|
78
|
+
"optional": true
|
|
75
79
|
}
|
|
76
80
|
},
|
|
77
81
|
"devDependencies": {
|