zen-fs-config 0.5.34 → 0.5.37
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/{backend-registry-7QGFWLM7.mjs → backend-registry-LELIKPQ4.mjs} +1 -1
- package/dist/{cache-wrapper-XQCPZJCD.mjs → cache-wrapper-S5HRLRQX.mjs} +11 -0
- package/dist/{chunk-UDEOYFWS.mjs → chunk-4RP2AXAB.mjs} +18 -5
- package/dist/index.d.mts +23 -5
- package/dist/index.d.ts +23 -5
- package/dist/index.js +40 -7
- package/dist/index.mjs +14 -5
- package/dist/zen-fs-config.js +97 -8
- package/package.json +2 -2
|
@@ -9,12 +9,23 @@ function wrapWithCache(backend, backendId, options) {
|
|
|
9
9
|
} else {
|
|
10
10
|
store = new MemoryCacheStore();
|
|
11
11
|
}
|
|
12
|
+
try {
|
|
13
|
+
const purge = store.purgeKeepFiles?.();
|
|
14
|
+
if (purge && typeof purge.catch === "function") {
|
|
15
|
+
purge.catch(() => {
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
} catch {
|
|
19
|
+
}
|
|
12
20
|
const wrapped = new CachedFileSystem(backend, store, {
|
|
13
21
|
ttlMs: options.ttlMs ?? 0
|
|
14
22
|
});
|
|
15
23
|
if (typeof backend.shouldSync === "function") {
|
|
16
24
|
wrapped.shouldSync = (...args) => backend.shouldSync(...args);
|
|
17
25
|
}
|
|
26
|
+
if (typeof backend.writeFileWithMtime === "function") {
|
|
27
|
+
wrapped.writeFileWithMtime = (path, data, mtimeMs) => backend.writeFileWithMtime(path, data, mtimeMs);
|
|
28
|
+
}
|
|
18
29
|
return wrapped;
|
|
19
30
|
}
|
|
20
31
|
export {
|
|
@@ -137,7 +137,9 @@ registerBackend("InMemory", async (options) => {
|
|
|
137
137
|
{ key: "maxSize", label: "Max Size (bytes)", type: "text", placeholder: "104857600" },
|
|
138
138
|
{ key: "label", label: "Label", type: "text", placeholder: "zen-fs-config-1" }
|
|
139
139
|
],
|
|
140
|
-
defaultOptions: { maxSize: "", label: "" }
|
|
140
|
+
defaultOptions: { maxSize: "", label: "" },
|
|
141
|
+
// identity = store label (maxSize is just client-side capacity tuning)
|
|
142
|
+
identityFields: ["label"]
|
|
141
143
|
});
|
|
142
144
|
registerBackend("IndexedDB", async (options) => {
|
|
143
145
|
const { IndexedDB } = await import("@zenfs/dom");
|
|
@@ -151,7 +153,9 @@ registerBackend("IndexedDB", async (options) => {
|
|
|
151
153
|
fields: [
|
|
152
154
|
{ key: "storeName", label: "Store Name", type: "text", placeholder: "zen-fs-config" }
|
|
153
155
|
],
|
|
154
|
-
defaultOptions: { storeName: "" }
|
|
156
|
+
defaultOptions: { storeName: "" },
|
|
157
|
+
// identity = IndexedDB store name
|
|
158
|
+
identityFields: ["storeName"]
|
|
155
159
|
});
|
|
156
160
|
var _memCache = /* @__PURE__ */ new Map();
|
|
157
161
|
function _cacheGet(key) {
|
|
@@ -260,7 +264,10 @@ registerBackend("GitHub", async (options) => {
|
|
|
260
264
|
{ key: "baseUrl", label: "API URL", type: "text", placeholder: "https://api.github.com" }
|
|
261
265
|
],
|
|
262
266
|
defaultOptions: { owner: "", repo: "", branch: "main", token: "", baseUrl: "" },
|
|
263
|
-
accountFields: ["token", "owner", "baseUrl"]
|
|
267
|
+
accountFields: ["token", "owner", "baseUrl"],
|
|
268
|
+
// identity = the Git repo location (token is auth, but included so that
|
|
269
|
+
// same-repo different-credential backends are still treated as distinct)
|
|
270
|
+
identityFields: ["owner", "repo", "branch", "baseUrl"]
|
|
264
271
|
});
|
|
265
272
|
registerBackend("Gitee", async (options) => {
|
|
266
273
|
const _gt = await importOptionalBackend("zen-fs-gitee");
|
|
@@ -290,7 +297,9 @@ registerBackend("Gitee", async (options) => {
|
|
|
290
297
|
{ key: "baseUrl", label: "API URL", type: "text", placeholder: "https://gitee.com/api/v5" }
|
|
291
298
|
],
|
|
292
299
|
defaultOptions: { owner: "", repo: "", branch: "master", token: "", baseUrl: "" },
|
|
293
|
-
accountFields: ["token", "owner", "baseUrl"]
|
|
300
|
+
accountFields: ["token", "owner", "baseUrl"],
|
|
301
|
+
// identity = the Git repo location
|
|
302
|
+
identityFields: ["owner", "repo", "branch", "baseUrl"]
|
|
294
303
|
});
|
|
295
304
|
registerBackend("RemoteStorage", async (options) => {
|
|
296
305
|
const _rs = await importOptionalBackend("zen-fs-remotestoragejs");
|
|
@@ -317,7 +326,11 @@ registerBackend("RemoteStorage", async (options) => {
|
|
|
317
326
|
{ key: "basePath", label: "Base Path", type: "text", placeholder: "/zen-fs-config/" }
|
|
318
327
|
],
|
|
319
328
|
defaultOptions: { href: "", token: "", basePath: "/" },
|
|
320
|
-
accountFields: ["href", "token"]
|
|
329
|
+
accountFields: ["href", "token"],
|
|
330
|
+
// identity = the RemoteStorage account + subtree. Client tuning (persistCache,
|
|
331
|
+
// cacheFile, preciseMtime, headers, timeout) is intentionally excluded so two
|
|
332
|
+
// connections to the same endpoint with different tuning are deduplicated.
|
|
333
|
+
identityFields: ["href", "token", "basePath"]
|
|
321
334
|
});
|
|
322
335
|
|
|
323
336
|
export {
|
package/dist/index.d.mts
CHANGED
|
@@ -81,8 +81,10 @@ declare function purgeMtimeSidecars(fs: PurgeableFS$1, options?: PurgeMtimeOptio
|
|
|
81
81
|
* metadata that keep real (empty) directories alive, and deleting them would
|
|
82
82
|
* lose directory structure.
|
|
83
83
|
*
|
|
84
|
-
* The intentional `.keep`
|
|
85
|
-
* alive) is protected and never deleted — see
|
|
84
|
+
* The intentional `.keep` placeholder at `/.meta/backends/.keep` (used to keep
|
|
85
|
+
* the backends directory alive) is protected and never deleted — see
|
|
86
|
+
* `protectedDirs`. Any other `.keep` (including `/.meta/.keep`, which some older
|
|
87
|
+
* builds leaked into the local primary) is treated as a leak and purged.
|
|
86
88
|
*/
|
|
87
89
|
/** Minimal async FS surface needed for the purge. */
|
|
88
90
|
interface PurgeableFS {
|
|
@@ -98,9 +100,10 @@ interface PurgeKeepOptions {
|
|
|
98
100
|
/** Report what would be deleted without unlinking anything. */
|
|
99
101
|
dryRun?: boolean;
|
|
100
102
|
/**
|
|
101
|
-
*
|
|
102
|
-
* Defaults to `['/.meta']` to protect the
|
|
103
|
-
* that keeps `/.meta/backends` alive.
|
|
103
|
+
* Exact paths (or directories and their subtrees) whose `.keep` files are
|
|
104
|
+
* NEVER deleted. Defaults to `['/.meta/backends/.keep']` to protect the
|
|
105
|
+
* single intentional placeholder that keeps `/.meta/backends` alive. A leaked
|
|
106
|
+
* `.keep` elsewhere (e.g. `/.meta/.keep`, `/docs/.keep`) is still purged.
|
|
104
107
|
*/
|
|
105
108
|
protectedDirs?: string[];
|
|
106
109
|
}
|
|
@@ -664,6 +667,21 @@ interface BackendMetadata {
|
|
|
664
667
|
* Fields not listed here are considered "storage location" fields.
|
|
665
668
|
*/
|
|
666
669
|
accountFields?: string[];
|
|
670
|
+
/**
|
|
671
|
+
* Option keys that uniquely identify the backend's STORAGE ENDPOINT.
|
|
672
|
+
* Two descriptors of the same `type` whose `identityFields` values match are
|
|
673
|
+
* considered the SAME replica (e.g. same RemoteStorage `href`+`token`, or
|
|
674
|
+
* same Gitee `owner`+`repo`+`branch`) and are deduplicated — even if they
|
|
675
|
+
* differ in client-side tuning options such as `basePath`, `persistCache`,
|
|
676
|
+
* `maxSize`, etc.
|
|
677
|
+
*
|
|
678
|
+
* When omitted, dedup falls back to the FULL options blob (legacy behavior),
|
|
679
|
+
* so only types that declare `identityFields` gain the connection-aware
|
|
680
|
+
* dedup. At minimum include the endpoint-identifying fields; auth/credential
|
|
681
|
+
* fields may be included or omitted depending on whether same-location
|
|
682
|
+
* different-credential backends should be treated as distinct.
|
|
683
|
+
*/
|
|
684
|
+
identityFields?: string[];
|
|
667
685
|
}
|
|
668
686
|
declare function registerBackend(type: string, factory: BackendFactory, metadata?: BackendMetadata): void;
|
|
669
687
|
declare function unregisterBackend(type: string): boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -81,8 +81,10 @@ declare function purgeMtimeSidecars(fs: PurgeableFS$1, options?: PurgeMtimeOptio
|
|
|
81
81
|
* metadata that keep real (empty) directories alive, and deleting them would
|
|
82
82
|
* lose directory structure.
|
|
83
83
|
*
|
|
84
|
-
* The intentional `.keep`
|
|
85
|
-
* alive) is protected and never deleted — see
|
|
84
|
+
* The intentional `.keep` placeholder at `/.meta/backends/.keep` (used to keep
|
|
85
|
+
* the backends directory alive) is protected and never deleted — see
|
|
86
|
+
* `protectedDirs`. Any other `.keep` (including `/.meta/.keep`, which some older
|
|
87
|
+
* builds leaked into the local primary) is treated as a leak and purged.
|
|
86
88
|
*/
|
|
87
89
|
/** Minimal async FS surface needed for the purge. */
|
|
88
90
|
interface PurgeableFS {
|
|
@@ -98,9 +100,10 @@ interface PurgeKeepOptions {
|
|
|
98
100
|
/** Report what would be deleted without unlinking anything. */
|
|
99
101
|
dryRun?: boolean;
|
|
100
102
|
/**
|
|
101
|
-
*
|
|
102
|
-
* Defaults to `['/.meta']` to protect the
|
|
103
|
-
* that keeps `/.meta/backends` alive.
|
|
103
|
+
* Exact paths (or directories and their subtrees) whose `.keep` files are
|
|
104
|
+
* NEVER deleted. Defaults to `['/.meta/backends/.keep']` to protect the
|
|
105
|
+
* single intentional placeholder that keeps `/.meta/backends` alive. A leaked
|
|
106
|
+
* `.keep` elsewhere (e.g. `/.meta/.keep`, `/docs/.keep`) is still purged.
|
|
104
107
|
*/
|
|
105
108
|
protectedDirs?: string[];
|
|
106
109
|
}
|
|
@@ -664,6 +667,21 @@ interface BackendMetadata {
|
|
|
664
667
|
* Fields not listed here are considered "storage location" fields.
|
|
665
668
|
*/
|
|
666
669
|
accountFields?: string[];
|
|
670
|
+
/**
|
|
671
|
+
* Option keys that uniquely identify the backend's STORAGE ENDPOINT.
|
|
672
|
+
* Two descriptors of the same `type` whose `identityFields` values match are
|
|
673
|
+
* considered the SAME replica (e.g. same RemoteStorage `href`+`token`, or
|
|
674
|
+
* same Gitee `owner`+`repo`+`branch`) and are deduplicated — even if they
|
|
675
|
+
* differ in client-side tuning options such as `basePath`, `persistCache`,
|
|
676
|
+
* `maxSize`, etc.
|
|
677
|
+
*
|
|
678
|
+
* When omitted, dedup falls back to the FULL options blob (legacy behavior),
|
|
679
|
+
* so only types that declare `identityFields` gain the connection-aware
|
|
680
|
+
* dedup. At minimum include the endpoint-identifying fields; auth/credential
|
|
681
|
+
* fields may be included or omitted depending on whether same-location
|
|
682
|
+
* different-credential backends should be treated as distinct.
|
|
683
|
+
*/
|
|
684
|
+
identityFields?: string[];
|
|
667
685
|
}
|
|
668
686
|
declare function registerBackend(type: string, factory: BackendFactory, metadata?: BackendMetadata): void;
|
|
669
687
|
declare function unregisterBackend(type: string): boolean;
|
package/dist/index.js
CHANGED
|
@@ -231,7 +231,9 @@ var init_backend_registry = __esm({
|
|
|
231
231
|
{ key: "maxSize", label: "Max Size (bytes)", type: "text", placeholder: "104857600" },
|
|
232
232
|
{ key: "label", label: "Label", type: "text", placeholder: "zen-fs-config-1" }
|
|
233
233
|
],
|
|
234
|
-
defaultOptions: { maxSize: "", label: "" }
|
|
234
|
+
defaultOptions: { maxSize: "", label: "" },
|
|
235
|
+
// identity = store label (maxSize is just client-side capacity tuning)
|
|
236
|
+
identityFields: ["label"]
|
|
235
237
|
});
|
|
236
238
|
registerBackend("IndexedDB", async (options) => {
|
|
237
239
|
const { IndexedDB } = await import("@zenfs/dom");
|
|
@@ -245,7 +247,9 @@ var init_backend_registry = __esm({
|
|
|
245
247
|
fields: [
|
|
246
248
|
{ key: "storeName", label: "Store Name", type: "text", placeholder: "zen-fs-config" }
|
|
247
249
|
],
|
|
248
|
-
defaultOptions: { storeName: "" }
|
|
250
|
+
defaultOptions: { storeName: "" },
|
|
251
|
+
// identity = IndexedDB store name
|
|
252
|
+
identityFields: ["storeName"]
|
|
249
253
|
});
|
|
250
254
|
_memCache = /* @__PURE__ */ new Map();
|
|
251
255
|
OPTIONAL_BACKEND_GLOBALS = {
|
|
@@ -309,7 +313,10 @@ var init_backend_registry = __esm({
|
|
|
309
313
|
{ key: "baseUrl", label: "API URL", type: "text", placeholder: "https://api.github.com" }
|
|
310
314
|
],
|
|
311
315
|
defaultOptions: { owner: "", repo: "", branch: "main", token: "", baseUrl: "" },
|
|
312
|
-
accountFields: ["token", "owner", "baseUrl"]
|
|
316
|
+
accountFields: ["token", "owner", "baseUrl"],
|
|
317
|
+
// identity = the Git repo location (token is auth, but included so that
|
|
318
|
+
// same-repo different-credential backends are still treated as distinct)
|
|
319
|
+
identityFields: ["owner", "repo", "branch", "baseUrl"]
|
|
313
320
|
});
|
|
314
321
|
registerBackend("Gitee", async (options) => {
|
|
315
322
|
const _gt = await importOptionalBackend("zen-fs-gitee");
|
|
@@ -339,7 +346,9 @@ var init_backend_registry = __esm({
|
|
|
339
346
|
{ key: "baseUrl", label: "API URL", type: "text", placeholder: "https://gitee.com/api/v5" }
|
|
340
347
|
],
|
|
341
348
|
defaultOptions: { owner: "", repo: "", branch: "master", token: "", baseUrl: "" },
|
|
342
|
-
accountFields: ["token", "owner", "baseUrl"]
|
|
349
|
+
accountFields: ["token", "owner", "baseUrl"],
|
|
350
|
+
// identity = the Git repo location
|
|
351
|
+
identityFields: ["owner", "repo", "branch", "baseUrl"]
|
|
343
352
|
});
|
|
344
353
|
registerBackend("RemoteStorage", async (options) => {
|
|
345
354
|
const _rs = await importOptionalBackend("zen-fs-remotestoragejs");
|
|
@@ -366,7 +375,11 @@ var init_backend_registry = __esm({
|
|
|
366
375
|
{ key: "basePath", label: "Base Path", type: "text", placeholder: "/zen-fs-config/" }
|
|
367
376
|
],
|
|
368
377
|
defaultOptions: { href: "", token: "", basePath: "/" },
|
|
369
|
-
accountFields: ["href", "token"]
|
|
378
|
+
accountFields: ["href", "token"],
|
|
379
|
+
// identity = the RemoteStorage account + subtree. Client tuning (persistCache,
|
|
380
|
+
// cacheFile, preciseMtime, headers, timeout) is intentionally excluded so two
|
|
381
|
+
// connections to the same endpoint with different tuning are deduplicated.
|
|
382
|
+
identityFields: ["href", "token", "basePath"]
|
|
370
383
|
});
|
|
371
384
|
}
|
|
372
385
|
});
|
|
@@ -385,12 +398,23 @@ function wrapWithCache(backend, backendId, options) {
|
|
|
385
398
|
} else {
|
|
386
399
|
store = new import_zen_fs_cache.MemoryCacheStore();
|
|
387
400
|
}
|
|
401
|
+
try {
|
|
402
|
+
const purge = store.purgeKeepFiles?.();
|
|
403
|
+
if (purge && typeof purge.catch === "function") {
|
|
404
|
+
purge.catch(() => {
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
} catch {
|
|
408
|
+
}
|
|
388
409
|
const wrapped = new import_zen_fs_cache.CachedFileSystem(backend, store, {
|
|
389
410
|
ttlMs: options.ttlMs ?? 0
|
|
390
411
|
});
|
|
391
412
|
if (typeof backend.shouldSync === "function") {
|
|
392
413
|
wrapped.shouldSync = (...args) => backend.shouldSync(...args);
|
|
393
414
|
}
|
|
415
|
+
if (typeof backend.writeFileWithMtime === "function") {
|
|
416
|
+
wrapped.writeFileWithMtime = (path, data, mtimeMs) => backend.writeFileWithMtime(path, data, mtimeMs);
|
|
417
|
+
}
|
|
394
418
|
return wrapped;
|
|
395
419
|
}
|
|
396
420
|
var import_zen_fs_cache;
|
|
@@ -1124,7 +1148,7 @@ function isKeepFile(fileName) {
|
|
|
1124
1148
|
return fileName === ".keep";
|
|
1125
1149
|
}
|
|
1126
1150
|
var S_IFDIR2 = 16384;
|
|
1127
|
-
var DEFAULT_PROTECTED_DIRS = ["/.meta"];
|
|
1151
|
+
var DEFAULT_PROTECTED_DIRS = ["/.meta/backends/.keep"];
|
|
1128
1152
|
function isDirectory2(stat) {
|
|
1129
1153
|
return typeof stat.mode === "number" && (stat.mode & S_IFDIR2) === S_IFDIR2;
|
|
1130
1154
|
}
|
|
@@ -1335,8 +1359,17 @@ function sortKeysDeep(obj) {
|
|
|
1335
1359
|
}
|
|
1336
1360
|
return sorted;
|
|
1337
1361
|
}
|
|
1362
|
+
function identityOptions(type, options) {
|
|
1363
|
+
const fields = getBackendMetadata(type)?.identityFields;
|
|
1364
|
+
if (!fields || fields.length === 0) return options ?? {};
|
|
1365
|
+
const subset = {};
|
|
1366
|
+
for (const f of fields) {
|
|
1367
|
+
if (options && options[f] !== void 0) subset[f] = options[f];
|
|
1368
|
+
}
|
|
1369
|
+
return subset;
|
|
1370
|
+
}
|
|
1338
1371
|
function backendDedupKey(desc) {
|
|
1339
|
-
return `${desc.type}:${stableOptionsKey(desc.options)}`;
|
|
1372
|
+
return `${desc.type}:${stableOptionsKey(identityOptions(desc.type, desc.options))}`;
|
|
1340
1373
|
}
|
|
1341
1374
|
function backendAgeRank(b) {
|
|
1342
1375
|
if (typeof b.createdAt === "number") return b.createdAt;
|
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-4RP2AXAB.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-LELIKPQ4.mjs")).wrapZenFSFileSystem({ backend: FolderBackend, path: dir, label: options.label });
|
|
355
355
|
},
|
|
356
356
|
{
|
|
357
357
|
type: "Folder",
|
|
@@ -693,7 +693,7 @@ function isKeepFile(fileName) {
|
|
|
693
693
|
return fileName === ".keep";
|
|
694
694
|
}
|
|
695
695
|
var S_IFDIR2 = 16384;
|
|
696
|
-
var DEFAULT_PROTECTED_DIRS = ["/.meta"];
|
|
696
|
+
var DEFAULT_PROTECTED_DIRS = ["/.meta/backends/.keep"];
|
|
697
697
|
function isDirectory2(stat) {
|
|
698
698
|
return typeof stat.mode === "number" && (stat.mode & S_IFDIR2) === S_IFDIR2;
|
|
699
699
|
}
|
|
@@ -904,8 +904,17 @@ function sortKeysDeep(obj) {
|
|
|
904
904
|
}
|
|
905
905
|
return sorted;
|
|
906
906
|
}
|
|
907
|
+
function identityOptions(type, options) {
|
|
908
|
+
const fields = getBackendMetadata(type)?.identityFields;
|
|
909
|
+
if (!fields || fields.length === 0) return options ?? {};
|
|
910
|
+
const subset = {};
|
|
911
|
+
for (const f of fields) {
|
|
912
|
+
if (options && options[f] !== void 0) subset[f] = options[f];
|
|
913
|
+
}
|
|
914
|
+
return subset;
|
|
915
|
+
}
|
|
907
916
|
function backendDedupKey(desc) {
|
|
908
|
-
return `${desc.type}:${stableOptionsKey(desc.options)}`;
|
|
917
|
+
return `${desc.type}:${stableOptionsKey(identityOptions(desc.type, desc.options))}`;
|
|
909
918
|
}
|
|
910
919
|
function backendAgeRank(b) {
|
|
911
920
|
if (typeof b.createdAt === "number") return b.createdAt;
|
|
@@ -1635,7 +1644,7 @@ var ConfigRepo = class {
|
|
|
1635
1644
|
const instance = await createBackend(desc);
|
|
1636
1645
|
let fsInstance = instance;
|
|
1637
1646
|
if (this.cacheOptions) {
|
|
1638
|
-
const { wrapWithCache } = await import("./cache-wrapper-
|
|
1647
|
+
const { wrapWithCache } = await import("./cache-wrapper-S5HRLRQX.mjs");
|
|
1639
1648
|
fsInstance = wrapWithCache(instance, desc.id, this.cacheOptions);
|
|
1640
1649
|
log2.log(`[ConfigRepo] Replica ${desc.id} wrapped with CachedFileSystem (store=${this.cacheOptions.storeType ?? "IdbCacheStore"})`);
|
|
1641
1650
|
}
|
package/dist/zen-fs-config.js
CHANGED
|
@@ -24544,7 +24544,9 @@ var ZenFSConfig = (() => {
|
|
|
24544
24544
|
{ key: "maxSize", label: "Max Size (bytes)", type: "text", placeholder: "104857600" },
|
|
24545
24545
|
{ key: "label", label: "Label", type: "text", placeholder: "zen-fs-config-1" }
|
|
24546
24546
|
],
|
|
24547
|
-
defaultOptions: { maxSize: "", label: "" }
|
|
24547
|
+
defaultOptions: { maxSize: "", label: "" },
|
|
24548
|
+
// identity = store label (maxSize is just client-side capacity tuning)
|
|
24549
|
+
identityFields: ["label"]
|
|
24548
24550
|
});
|
|
24549
24551
|
registerBackend("IndexedDB", async (options) => {
|
|
24550
24552
|
const { IndexedDB: IndexedDB2 } = await Promise.resolve().then(() => (init_dist5(), dist_exports2));
|
|
@@ -24558,7 +24560,9 @@ var ZenFSConfig = (() => {
|
|
|
24558
24560
|
fields: [
|
|
24559
24561
|
{ key: "storeName", label: "Store Name", type: "text", placeholder: "zen-fs-config" }
|
|
24560
24562
|
],
|
|
24561
|
-
defaultOptions: { storeName: "" }
|
|
24563
|
+
defaultOptions: { storeName: "" },
|
|
24564
|
+
// identity = IndexedDB store name
|
|
24565
|
+
identityFields: ["storeName"]
|
|
24562
24566
|
});
|
|
24563
24567
|
_memCache = /* @__PURE__ */ new Map();
|
|
24564
24568
|
OPTIONAL_BACKEND_GLOBALS = {
|
|
@@ -24622,7 +24626,10 @@ var ZenFSConfig = (() => {
|
|
|
24622
24626
|
{ key: "baseUrl", label: "API URL", type: "text", placeholder: "https://api.github.com" }
|
|
24623
24627
|
],
|
|
24624
24628
|
defaultOptions: { owner: "", repo: "", branch: "main", token: "", baseUrl: "" },
|
|
24625
|
-
accountFields: ["token", "owner", "baseUrl"]
|
|
24629
|
+
accountFields: ["token", "owner", "baseUrl"],
|
|
24630
|
+
// identity = the Git repo location (token is auth, but included so that
|
|
24631
|
+
// same-repo different-credential backends are still treated as distinct)
|
|
24632
|
+
identityFields: ["owner", "repo", "branch", "baseUrl"]
|
|
24626
24633
|
});
|
|
24627
24634
|
registerBackend("Gitee", async (options) => {
|
|
24628
24635
|
const _gt = await importOptionalBackend("zen-fs-gitee");
|
|
@@ -24652,7 +24659,9 @@ var ZenFSConfig = (() => {
|
|
|
24652
24659
|
{ key: "baseUrl", label: "API URL", type: "text", placeholder: "https://gitee.com/api/v5" }
|
|
24653
24660
|
],
|
|
24654
24661
|
defaultOptions: { owner: "", repo: "", branch: "master", token: "", baseUrl: "" },
|
|
24655
|
-
accountFields: ["token", "owner", "baseUrl"]
|
|
24662
|
+
accountFields: ["token", "owner", "baseUrl"],
|
|
24663
|
+
// identity = the Git repo location
|
|
24664
|
+
identityFields: ["owner", "repo", "branch", "baseUrl"]
|
|
24656
24665
|
});
|
|
24657
24666
|
registerBackend("RemoteStorage", async (options) => {
|
|
24658
24667
|
const _rs = await importOptionalBackend("zen-fs-remotestoragejs");
|
|
@@ -24679,7 +24688,11 @@ var ZenFSConfig = (() => {
|
|
|
24679
24688
|
{ key: "basePath", label: "Base Path", type: "text", placeholder: "/zen-fs-config/" }
|
|
24680
24689
|
],
|
|
24681
24690
|
defaultOptions: { href: "", token: "", basePath: "/" },
|
|
24682
|
-
accountFields: ["href", "token"]
|
|
24691
|
+
accountFields: ["href", "token"],
|
|
24692
|
+
// identity = the RemoteStorage account + subtree. Client tuning (persistCache,
|
|
24693
|
+
// cacheFile, preciseMtime, headers, timeout) is intentionally excluded so two
|
|
24694
|
+
// connections to the same endpoint with different tuning are deduplicated.
|
|
24695
|
+
identityFields: ["href", "token", "basePath"]
|
|
24683
24696
|
});
|
|
24684
24697
|
}
|
|
24685
24698
|
});
|
|
@@ -24886,10 +24899,35 @@ var ZenFSConfig = (() => {
|
|
|
24886
24899
|
}
|
|
24887
24900
|
async exists(path) {
|
|
24888
24901
|
try {
|
|
24902
|
+
const innerType = this.inner?.constructor?.name ?? typeof this.inner;
|
|
24903
|
+
const hasStat = typeof this.inner?.stat === "function";
|
|
24904
|
+
const hasExists = typeof this.inner?.exists === "function";
|
|
24905
|
+
const backendName = this.inner?.backendName ?? this.inner?.constructor?.name ?? "unknown";
|
|
24906
|
+
console.log(`[CACHE-EXISTS] exists(${path}): inner=${innerType} backend=${backendName} hasStat=${hasStat} hasExists=${hasExists}`);
|
|
24907
|
+
const statKey = `stat:${path}`;
|
|
24908
|
+
const cachedStat = await this.store.get(statKey);
|
|
24909
|
+
if (cachedStat) {
|
|
24910
|
+
const age = Date.now() - cachedStat.cachedAt;
|
|
24911
|
+
console.log(`[CACHE-EXISTS] exists(${path}): cached stat entry found, age=${age}ms, ttlMs=${this.ttlMs}`);
|
|
24912
|
+
} else {
|
|
24913
|
+
console.log(`[CACHE-EXISTS] exists(${path}): no cached stat entry`);
|
|
24914
|
+
}
|
|
24915
|
+
if (hasExists) {
|
|
24916
|
+
console.log(`[CACHE-EXISTS] exists(${path}): \u2192 calling inner.exists() [backend=${backendName}]`);
|
|
24917
|
+
const result = await this.inner.exists(path);
|
|
24918
|
+
console.log(`[CACHE-EXISTS] exists(${path}): \u2190 inner.exists() [backend=${backendName}] \u2192 ${result}`);
|
|
24919
|
+
return result;
|
|
24920
|
+
}
|
|
24921
|
+
console.log(`[CACHE-EXISTS] exists(${path}): no inner.exists(), falling back to inner.stat() [backend=${backendName}]`);
|
|
24889
24922
|
await this.inner.stat(path);
|
|
24923
|
+
console.log(`[CACHE-EXISTS] exists(${path}): \u2190 inner.stat() [backend=${backendName}] \u2192 OK (exists)`);
|
|
24890
24924
|
return true;
|
|
24891
24925
|
} catch (err2) {
|
|
24892
|
-
if (this.isNotFound(err2))
|
|
24926
|
+
if (this.isNotFound(err2)) {
|
|
24927
|
+
console.log(`[CACHE-EXISTS] exists(${path}): \u2190 inner.stat() \u2192 NotFound`);
|
|
24928
|
+
return false;
|
|
24929
|
+
}
|
|
24930
|
+
console.log(`[CACHE-EXISTS] exists(${path}): \u2190 inner.stat() \u2192 ERROR: ${err2}`);
|
|
24893
24931
|
throw err2;
|
|
24894
24932
|
}
|
|
24895
24933
|
}
|
|
@@ -24922,7 +24960,10 @@ var ZenFSConfig = (() => {
|
|
|
24922
24960
|
await this.invalidateMeta(path);
|
|
24923
24961
|
}
|
|
24924
24962
|
async unlink(path) {
|
|
24963
|
+
const backendName = this.inner?.backendName ?? this.inner?.constructor?.name ?? "unknown";
|
|
24964
|
+
console.log(`[CACHE-UNLINK] unlink(${path}): calling inner.unlink() [backend=${backendName}]`);
|
|
24925
24965
|
await this.inner.unlink(path);
|
|
24966
|
+
console.log(`[CACHE-UNLINK] unlink(${path}): inner.unlink() OK [backend=${backendName}], invalidating cache`);
|
|
24926
24967
|
await this.invalidate(path);
|
|
24927
24968
|
}
|
|
24928
24969
|
async mkdir(path, options) {
|
|
@@ -24981,6 +25022,12 @@ var ZenFSConfig = (() => {
|
|
|
24981
25022
|
async clear() {
|
|
24982
25023
|
this.store.clear();
|
|
24983
25024
|
}
|
|
25025
|
+
async purgeKeepFiles() {
|
|
25026
|
+
for (const key of Array.from(this.store.keys())) {
|
|
25027
|
+
const base = key === "/" ? "" : key.split("/").pop() ?? "";
|
|
25028
|
+
if (base === ".keep") this.store.delete(key);
|
|
25029
|
+
}
|
|
25030
|
+
}
|
|
24984
25031
|
};
|
|
24985
25032
|
IdbCacheStore = class {
|
|
24986
25033
|
constructor(prefix = "zen-fs-cache:", dbName = "zen-fs-cache", storeName = "cache") {
|
|
@@ -25061,6 +25108,28 @@ var ZenFSConfig = (() => {
|
|
|
25061
25108
|
} catch {
|
|
25062
25109
|
}
|
|
25063
25110
|
}
|
|
25111
|
+
async purgeKeepFiles() {
|
|
25112
|
+
try {
|
|
25113
|
+
const db = await this.open();
|
|
25114
|
+
await new Promise((resolve4, reject) => {
|
|
25115
|
+
const tx = db.transaction(this.storeName, "readwrite");
|
|
25116
|
+
const store = tx.objectStore(this.storeName);
|
|
25117
|
+
const req = store.getAllKeys();
|
|
25118
|
+
req.onsuccess = () => {
|
|
25119
|
+
const keys = req.result ?? [];
|
|
25120
|
+
for (const raw of keys) {
|
|
25121
|
+
const path = raw.startsWith(this.prefix) ? raw.slice(this.prefix.length) : raw;
|
|
25122
|
+
const base = path === "/" ? "" : path.split("/").pop() ?? "";
|
|
25123
|
+
if (base === ".keep") store.delete(raw);
|
|
25124
|
+
}
|
|
25125
|
+
};
|
|
25126
|
+
req.onerror = () => reject(req.error);
|
|
25127
|
+
tx.oncomplete = () => resolve4();
|
|
25128
|
+
tx.onerror = () => reject(tx.error);
|
|
25129
|
+
});
|
|
25130
|
+
} catch {
|
|
25131
|
+
}
|
|
25132
|
+
}
|
|
25064
25133
|
};
|
|
25065
25134
|
}
|
|
25066
25135
|
});
|
|
@@ -25079,12 +25148,23 @@ var ZenFSConfig = (() => {
|
|
|
25079
25148
|
} else {
|
|
25080
25149
|
store = new MemoryCacheStore();
|
|
25081
25150
|
}
|
|
25151
|
+
try {
|
|
25152
|
+
const purge = store.purgeKeepFiles?.();
|
|
25153
|
+
if (purge && typeof purge.catch === "function") {
|
|
25154
|
+
purge.catch(() => {
|
|
25155
|
+
});
|
|
25156
|
+
}
|
|
25157
|
+
} catch {
|
|
25158
|
+
}
|
|
25082
25159
|
const wrapped = new CachedFileSystem(backend, store, {
|
|
25083
25160
|
ttlMs: options.ttlMs ?? 0
|
|
25084
25161
|
});
|
|
25085
25162
|
if (typeof backend.shouldSync === "function") {
|
|
25086
25163
|
wrapped.shouldSync = (...args) => backend.shouldSync(...args);
|
|
25087
25164
|
}
|
|
25165
|
+
if (typeof backend.writeFileWithMtime === "function") {
|
|
25166
|
+
wrapped.writeFileWithMtime = (path, data, mtimeMs) => backend.writeFileWithMtime(path, data, mtimeMs);
|
|
25167
|
+
}
|
|
25088
25168
|
return wrapped;
|
|
25089
25169
|
}
|
|
25090
25170
|
var init_cache_wrapper = __esm({
|
|
@@ -27001,7 +27081,7 @@ var ZenFSConfig = (() => {
|
|
|
27001
27081
|
return fileName === ".keep";
|
|
27002
27082
|
}
|
|
27003
27083
|
var S_IFDIR3 = 16384;
|
|
27004
|
-
var DEFAULT_PROTECTED_DIRS = ["/.meta"];
|
|
27084
|
+
var DEFAULT_PROTECTED_DIRS = ["/.meta/backends/.keep"];
|
|
27005
27085
|
function isDirectory4(stat5) {
|
|
27006
27086
|
return typeof stat5.mode === "number" && (stat5.mode & S_IFDIR3) === S_IFDIR3;
|
|
27007
27087
|
}
|
|
@@ -27212,8 +27292,17 @@ var ZenFSConfig = (() => {
|
|
|
27212
27292
|
}
|
|
27213
27293
|
return sorted;
|
|
27214
27294
|
}
|
|
27295
|
+
function identityOptions(type, options) {
|
|
27296
|
+
const fields = getBackendMetadata(type)?.identityFields;
|
|
27297
|
+
if (!fields || fields.length === 0) return options ?? {};
|
|
27298
|
+
const subset = {};
|
|
27299
|
+
for (const f of fields) {
|
|
27300
|
+
if (options && options[f] !== void 0) subset[f] = options[f];
|
|
27301
|
+
}
|
|
27302
|
+
return subset;
|
|
27303
|
+
}
|
|
27215
27304
|
function backendDedupKey(desc) {
|
|
27216
|
-
return `${desc.type}:${stableOptionsKey(desc.options)}`;
|
|
27305
|
+
return `${desc.type}:${stableOptionsKey(identityOptions(desc.type, desc.options))}`;
|
|
27217
27306
|
}
|
|
27218
27307
|
function backendAgeRank(b) {
|
|
27219
27308
|
if (typeof b.createdAt === "number") return b.createdAt;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zen-fs-config",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.37",
|
|
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",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"tsup": "^8.5.1",
|
|
81
81
|
"typescript": "^5.9.3",
|
|
82
82
|
"vitest": "^1.6.1",
|
|
83
|
-
"zen-fs-cache": "^1.0.
|
|
83
|
+
"zen-fs-cache": "^1.0.6",
|
|
84
84
|
"zen-fs-sync": "^0.4.9"
|
|
85
85
|
}
|
|
86
86
|
}
|