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.
@@ -9,7 +9,7 @@ import {
9
9
  registerBackend,
10
10
  unregisterBackend,
11
11
  wrapZenFSFileSystem
12
- } from "./chunk-4RP2AXAB.mjs";
12
+ } from "./chunk-NSEXSKVW.mjs";
13
13
  export {
14
14
  createBackend,
15
15
  getAccountFields,
@@ -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.set(type, metadata);
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.set(type, metadata);
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 meta = getBackendMetadata(type);
1364
- const fields = meta?.identityFields;
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
- log2.log(`[DEDUP-DIAG] removed dup ${dupId} from replica ${replicaId}`);
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 (e) {
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-4RP2AXAB.mjs";
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-LELIKPQ4.mjs")).wrapZenFSFileSystem({ backend: FolderBackend, path: dir, label: options.label });
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 meta = getBackendMetadata(type);
909
- const fields = meta?.identityFields;
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
- log2.log(`[DEDUP-DIAG] removed dup ${dupId} from replica ${replicaId}`);
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 (e) {
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);
@@ -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.set(type, metadata);
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 meta = getBackendMetadata(type);
27297
- const fields = meta?.identityFields;
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
- log6.log(`[DEDUP-DIAG] removed dup ${dupId} from replica ${replicaId}`);
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 (e) {
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.38",
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": {