vfs-kit 1.0.1 → 1.0.2
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/LICENSE +21 -21
- package/README.md +145 -35
- package/dist/VfsAdapter-BWjniD9Y.d.mts +57 -0
- package/dist/VfsAdapter-DOBt_TyL.d.ts +57 -0
- package/dist/VfsEngine-B6nhgyjQ.d.mts +152 -0
- package/dist/VfsEngine-DLx0iUpi.d.ts +152 -0
- package/dist/VfsNode-D10gxL5W.d.mts +48 -0
- package/dist/VfsNode-D10gxL5W.d.ts +48 -0
- package/dist/adapters/index.d.mts +201 -0
- package/dist/adapters/index.d.ts +201 -0
- package/dist/adapters/index.js +1159 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/index.mjs +1159 -0
- package/dist/adapters/index.mjs.map +1 -0
- package/dist/chunk-2FEJBM4N.js +60 -0
- package/dist/chunk-2FEJBM4N.js.map +1 -0
- package/dist/chunk-7OQI6PNM.mjs +60 -0
- package/dist/chunk-7OQI6PNM.mjs.map +1 -0
- package/dist/chunk-ALWOZGZI.mjs +23 -0
- package/dist/chunk-ALWOZGZI.mjs.map +1 -0
- package/dist/chunk-POSVS4C7.mjs +531 -0
- package/dist/chunk-POSVS4C7.mjs.map +1 -0
- package/dist/chunk-R3ROYAMW.js +23 -0
- package/dist/chunk-R3ROYAMW.js.map +1 -0
- package/dist/chunk-SWRBVSS6.mjs +16 -0
- package/dist/chunk-SWRBVSS6.mjs.map +1 -0
- package/dist/chunk-U2CKTXY7.js +16 -0
- package/dist/chunk-U2CKTXY7.js.map +1 -0
- package/dist/chunk-WZVVI3HX.js +531 -0
- package/dist/chunk-WZVVI3HX.js.map +1 -0
- package/dist/components/index.d.mts +193 -0
- package/dist/components/index.d.ts +193 -0
- package/dist/components/index.js +1197 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +1197 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/hooks/index.d.mts +120 -0
- package/dist/hooks/index.d.ts +120 -0
- package/dist/hooks/index.js +51 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +51 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/index.d.mts +42 -0
- package/dist/index.d.ts +38 -3
- package/dist/index.js +528 -13
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +530 -0
- package/dist/index.mjs.map +1 -0
- package/dist/useVfsTabs-ZHDaLrM1.d.mts +39 -0
- package/dist/useVfsTabs-ZHDaLrM1.d.ts +39 -0
- package/package.json +59 -61
- package/dist/index.cjs +0 -43
- package/dist/index.d.cts +0 -7
- package/index.js +0 -7
- package/src/components/TreeView.tsx +0 -5
- package/src/components/index.ts +0 -1
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useVfs.ts +0 -3
- package/src/index.ts +0 -2
- package/tsconfig.json +0 -44
- package/tsup.config.ts +0 -10
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
VfsAdapter
|
|
4
|
+
} from "./chunk-ALWOZGZI.mjs";
|
|
5
|
+
import {
|
|
6
|
+
VfsContext
|
|
7
|
+
} from "./chunk-SWRBVSS6.mjs";
|
|
8
|
+
import {
|
|
9
|
+
__async
|
|
10
|
+
} from "./chunk-7OQI6PNM.mjs";
|
|
11
|
+
|
|
12
|
+
// src/core/VfsEngine.ts
|
|
13
|
+
var VfsEngine = class {
|
|
14
|
+
constructor(adapter, config) {
|
|
15
|
+
this.unsubscribeAdapter = null;
|
|
16
|
+
this.version = 0;
|
|
17
|
+
this.pending = false;
|
|
18
|
+
this.lastError = null;
|
|
19
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
20
|
+
this.adapter = adapter;
|
|
21
|
+
this.config = {
|
|
22
|
+
sessionId: config.sessionId,
|
|
23
|
+
allowDuplicateNames: (_a = config.allowDuplicateNames) != null ? _a : false,
|
|
24
|
+
duplicateResolution: (_b = config.duplicateResolution) != null ? _b : "throw",
|
|
25
|
+
checkPermission: (_c = config.checkPermission) != null ? _c : (() => true),
|
|
26
|
+
history: {
|
|
27
|
+
maxSnapshots: (_e = (_d = config.history) == null ? void 0 : _d.maxSnapshots) != null ? _e : 50,
|
|
28
|
+
autosave: (_g = (_f = config.history) == null ? void 0 : _f.autosave) != null ? _g : { enabled: false, intervalMs: 3e4 }
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
this.cache = {
|
|
32
|
+
nodes: /* @__PURE__ */ new Map(),
|
|
33
|
+
childIndex: /* @__PURE__ */ new Map(),
|
|
34
|
+
loadedSets: /* @__PURE__ */ new Set()
|
|
35
|
+
};
|
|
36
|
+
this.listeners = {};
|
|
37
|
+
}
|
|
38
|
+
init(strategy = "hybrid") {
|
|
39
|
+
return __async(this, null, function* () {
|
|
40
|
+
this.unsubscribeAdapter = this.adapter.onChanged((change) => {
|
|
41
|
+
this.handleAdapterChange(change);
|
|
42
|
+
});
|
|
43
|
+
if (strategy === "eager") {
|
|
44
|
+
const all = yield this.adapter.getNodes([]);
|
|
45
|
+
this.hydrateCache(all);
|
|
46
|
+
} else if (strategy === "hybrid") {
|
|
47
|
+
yield this.loadChildren(null);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
destroy() {
|
|
52
|
+
var _a;
|
|
53
|
+
(_a = this.unsubscribeAdapter) == null ? void 0 : _a.call(this);
|
|
54
|
+
this.cache.nodes.clear();
|
|
55
|
+
this.cache.childIndex.clear();
|
|
56
|
+
this.cache.loadedSets.clear();
|
|
57
|
+
}
|
|
58
|
+
subscribe(onStoreChange) {
|
|
59
|
+
return this.on("change", onStoreChange);
|
|
60
|
+
}
|
|
61
|
+
// ── Command entry point ────────────────────────────────────────────────
|
|
62
|
+
execute(command) {
|
|
63
|
+
return __async(this, null, function* () {
|
|
64
|
+
const targetIds = this.extractTargetIds(command);
|
|
65
|
+
for (const id of targetIds) {
|
|
66
|
+
const node = this.cache.nodes.get(id);
|
|
67
|
+
if (node && !this.config.checkPermission(node, command.op)) {
|
|
68
|
+
throw new Error(`Permission denied: ${command.op} on node ${id}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
this.pending = true;
|
|
72
|
+
this.lastError = null;
|
|
73
|
+
this.emit("pending", { command });
|
|
74
|
+
try {
|
|
75
|
+
yield this.runCommand(command);
|
|
76
|
+
this.pending = false;
|
|
77
|
+
this.emit("settled", { command, success: true });
|
|
78
|
+
} catch (err) {
|
|
79
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
80
|
+
this.lastError = error;
|
|
81
|
+
this.pending = false;
|
|
82
|
+
this.emit("error", { command, error });
|
|
83
|
+
this.emit("settled", { command, success: false });
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// ── Command router ─────────────────────────────────────────────────────
|
|
89
|
+
runCommand(command) {
|
|
90
|
+
return __async(this, null, function* () {
|
|
91
|
+
switch (command.op) {
|
|
92
|
+
case "create":
|
|
93
|
+
return this.runCreate(command);
|
|
94
|
+
case "rename":
|
|
95
|
+
return this.runRename(command);
|
|
96
|
+
case "delete":
|
|
97
|
+
return this.runDelete(command);
|
|
98
|
+
case "restore":
|
|
99
|
+
return this.runRestore(command);
|
|
100
|
+
case "purge":
|
|
101
|
+
return this.runPurge(command);
|
|
102
|
+
case "move":
|
|
103
|
+
return this.runMove(command);
|
|
104
|
+
case "write":
|
|
105
|
+
return this.runWrite(command);
|
|
106
|
+
case "lock":
|
|
107
|
+
return this.runLock(command);
|
|
108
|
+
case "unlock":
|
|
109
|
+
return this.runUnlock(command);
|
|
110
|
+
case "reorder":
|
|
111
|
+
return this.runReorder(command);
|
|
112
|
+
case "snapshot":
|
|
113
|
+
return this.runSnapshot(command);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
// ── Command implementations ────────────────────────────────────────────
|
|
118
|
+
runCreate(command) {
|
|
119
|
+
return __async(this, null, function* () {
|
|
120
|
+
if (!this.config.allowDuplicateNames) {
|
|
121
|
+
yield this.assertNoDuplicate(command.parentId, command.name, command.kind);
|
|
122
|
+
}
|
|
123
|
+
const node = command.kind === "file" ? yield this.adapter.createFile({
|
|
124
|
+
parentId: command.parentId,
|
|
125
|
+
name: command.name,
|
|
126
|
+
mimeType: command.mimeType,
|
|
127
|
+
meta: command.meta
|
|
128
|
+
}) : yield this.adapter.createFolder({
|
|
129
|
+
parentId: command.parentId,
|
|
130
|
+
name: command.name,
|
|
131
|
+
meta: command.meta
|
|
132
|
+
});
|
|
133
|
+
this.cacheNode(node);
|
|
134
|
+
this.emit("created", node);
|
|
135
|
+
this.emitChange("create", [node.id]);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
runRename(command) {
|
|
139
|
+
return __async(this, null, function* () {
|
|
140
|
+
const existing = yield this.resolveNode(command.id);
|
|
141
|
+
if (!this.config.allowDuplicateNames) {
|
|
142
|
+
yield this.assertNoDuplicate(existing.parentId, command.newName, existing.kind, command.id);
|
|
143
|
+
}
|
|
144
|
+
const updated = yield this.adapter.updateNode(command.id, { name: command.newName });
|
|
145
|
+
this.cacheNode(updated);
|
|
146
|
+
this.emit("renamed", updated);
|
|
147
|
+
this.emitChange("rename", [command.id]);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
runDelete(command) {
|
|
151
|
+
return __async(this, null, function* () {
|
|
152
|
+
var _a;
|
|
153
|
+
yield this.adapter.deleteNodes(command.ids, command.permanent);
|
|
154
|
+
for (const id of command.ids) this.evictNode(id);
|
|
155
|
+
this.emit("deleted", { ids: command.ids, permanent: (_a = command.permanent) != null ? _a : false });
|
|
156
|
+
this.emitChange("delete", command.ids);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
runRestore(command) {
|
|
160
|
+
return __async(this, null, function* () {
|
|
161
|
+
const restored = yield Promise.all(command.ids.map((id) => this.adapter.restoreNode(id)));
|
|
162
|
+
for (const node of restored) this.cacheNode(node);
|
|
163
|
+
this.emit("restored", restored);
|
|
164
|
+
this.emitChange("restore", command.ids);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
runPurge(command) {
|
|
168
|
+
return __async(this, null, function* () {
|
|
169
|
+
yield Promise.all(command.ids.map((id) => this.adapter.purgeNode(id)));
|
|
170
|
+
for (const id of command.ids) this.evictNode(id);
|
|
171
|
+
this.emit("purged", { ids: command.ids });
|
|
172
|
+
this.emitChange("purge", command.ids);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
runMove(command) {
|
|
176
|
+
return __async(this, null, function* () {
|
|
177
|
+
var _a, _b, _c, _d;
|
|
178
|
+
const oldParentIds = new Map(
|
|
179
|
+
command.ids.map((id) => {
|
|
180
|
+
var _a2, _b2;
|
|
181
|
+
return [id, (_b2 = (_a2 = this.cache.nodes.get(id)) == null ? void 0 : _a2.parentId) != null ? _b2 : null];
|
|
182
|
+
})
|
|
183
|
+
);
|
|
184
|
+
yield this.adapter.moveNodes(command.ids, command.newParentId);
|
|
185
|
+
for (const id of command.ids) {
|
|
186
|
+
const oldParentKey = (_a = oldParentIds.get(id)) != null ? _a : null;
|
|
187
|
+
const oldSiblings = (_b = this.cache.childIndex.get(oldParentKey != null ? oldParentKey : "root")) != null ? _b : [];
|
|
188
|
+
this.cache.childIndex.set(
|
|
189
|
+
oldParentKey != null ? oldParentKey : "root",
|
|
190
|
+
oldSiblings.filter((s) => s !== id)
|
|
191
|
+
);
|
|
192
|
+
const updated = yield this.adapter.getNodeById(id);
|
|
193
|
+
if (!updated) continue;
|
|
194
|
+
this.cache.nodes.set(updated.id, updated);
|
|
195
|
+
const newParentKey = (_c = updated.parentId) != null ? _c : "root";
|
|
196
|
+
const newSiblings = (_d = this.cache.childIndex.get(newParentKey)) != null ? _d : [];
|
|
197
|
+
if (!newSiblings.includes(id)) {
|
|
198
|
+
this.cache.childIndex.set(newParentKey, [...newSiblings, id]);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const movedNodes = command.ids.map((id) => this.cache.nodes.get(id)).filter((n) => !!n);
|
|
202
|
+
this.emit("moved", movedNodes);
|
|
203
|
+
this.emitChange("move", command.ids);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
runWrite(command) {
|
|
207
|
+
return __async(this, null, function* () {
|
|
208
|
+
const node = yield this.resolveNode(command.id);
|
|
209
|
+
if (node.kind !== "file") throw new Error(`Cannot write to folder: ${command.id}`);
|
|
210
|
+
if (node.lockedBy && node.lockedBy !== this.config.sessionId) {
|
|
211
|
+
throw new Error(`File ${command.id} is locked by another session`);
|
|
212
|
+
}
|
|
213
|
+
yield this.adapter.writeFile(command.id, command.content);
|
|
214
|
+
const updated = yield this.adapter.updateNode(command.id, {
|
|
215
|
+
size: command.content.byteLength,
|
|
216
|
+
updatedAt: Date.now()
|
|
217
|
+
});
|
|
218
|
+
this.cacheNode(updated);
|
|
219
|
+
this.emit("written", { id: command.id });
|
|
220
|
+
this.emitChange("write", [command.id]);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
runLock(command) {
|
|
224
|
+
return __async(this, null, function* () {
|
|
225
|
+
const locked = yield Promise.all(
|
|
226
|
+
command.ids.map((id) => this.adapter.lockNode(id, this.config.sessionId))
|
|
227
|
+
);
|
|
228
|
+
for (const node of locked) this.cacheNode(node);
|
|
229
|
+
this.emit("locked", locked);
|
|
230
|
+
this.emitChange("lock", command.ids);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
runUnlock(command) {
|
|
234
|
+
return __async(this, null, function* () {
|
|
235
|
+
const unlocked = yield Promise.all(
|
|
236
|
+
command.ids.map((id) => this.adapter.unlockNode(id, this.config.sessionId))
|
|
237
|
+
);
|
|
238
|
+
for (const node of unlocked) this.cacheNode(node);
|
|
239
|
+
this.emit("unlocked", unlocked);
|
|
240
|
+
this.emitChange("unlock", command.ids);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
runReorder(command) {
|
|
244
|
+
return __async(this, null, function* () {
|
|
245
|
+
yield this.adapter.setOrder(command.parentId, command.orderedIds);
|
|
246
|
+
this.emit("reordered", { parentId: command.parentId, orderedIds: command.orderedIds });
|
|
247
|
+
this.emitChange("reorder", []);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
runSnapshot(command) {
|
|
251
|
+
return __async(this, null, function* () {
|
|
252
|
+
if (!this.adapter.supportsHistory) {
|
|
253
|
+
throw new Error("Adapter does not support history");
|
|
254
|
+
}
|
|
255
|
+
const content = yield this.adapter.readFile(command.fileId);
|
|
256
|
+
const snapshot = yield this.adapter.saveSnapshot(command.fileId, content, command.label);
|
|
257
|
+
this.emit("snapshot", snapshot);
|
|
258
|
+
this.emitChange("snapshot", [command.fileId]);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
// ── Read API ───────────────────────────────────────────────────────────
|
|
262
|
+
getNode(id) {
|
|
263
|
+
return __async(this, null, function* () {
|
|
264
|
+
if (this.cache.nodes.has(id)) return this.cache.nodes.get(id);
|
|
265
|
+
const node = yield this.adapter.getNodeById(id);
|
|
266
|
+
if (node) this.cacheNode(node);
|
|
267
|
+
return node;
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
getChildren(parentId, options) {
|
|
271
|
+
return __async(this, null, function* () {
|
|
272
|
+
var _a;
|
|
273
|
+
if (!this.cache.loadedSets.has(parentId != null ? parentId : null)) {
|
|
274
|
+
yield this.loadChildren(parentId);
|
|
275
|
+
}
|
|
276
|
+
const ids = (_a = this.cache.childIndex.get(parentId != null ? parentId : "root")) != null ? _a : [];
|
|
277
|
+
const nodes = ids.map((id) => this.cache.nodes.get(id)).filter((n) => !!n).filter((n) => (options == null ? void 0 : options.includeTrashed) ? true : n.deletedAt === null);
|
|
278
|
+
return this.applySortOrder(nodes, parentId);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
getPath(id) {
|
|
282
|
+
return __async(this, null, function* () {
|
|
283
|
+
const parts = [];
|
|
284
|
+
let current = yield this.getNode(id);
|
|
285
|
+
while (current) {
|
|
286
|
+
parts.unshift(current.name);
|
|
287
|
+
current = current.parentId ? yield this.getNode(current.parentId) : null;
|
|
288
|
+
}
|
|
289
|
+
return "/" + parts.join("/");
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
readFile(id) {
|
|
293
|
+
return __async(this, null, function* () {
|
|
294
|
+
return this.adapter.readFile(id);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
search(query, options) {
|
|
298
|
+
return __async(this, null, function* () {
|
|
299
|
+
return this.adapter.search(query, options);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
getTrashed() {
|
|
303
|
+
return __async(this, null, function* () {
|
|
304
|
+
return this.adapter.getTrashed();
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
getSnapshots(fileId) {
|
|
308
|
+
return __async(this, null, function* () {
|
|
309
|
+
if (!this.adapter.supportsHistory) throw new Error("Adapter does not support history");
|
|
310
|
+
return this.adapter.getSnapshots(fileId);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
// ── Sort resolution ────────────────────────────────────────────────────
|
|
314
|
+
applySortOrder(nodes, parentId) {
|
|
315
|
+
return __async(this, null, function* () {
|
|
316
|
+
const order = yield this.adapter.getOrder(parentId);
|
|
317
|
+
if (!order) return this.autoSort(nodes);
|
|
318
|
+
const validIds = new Set(nodes.map((n) => n.id));
|
|
319
|
+
const orderedIds = order.orderedIds.filter((id) => validIds.has(id));
|
|
320
|
+
const orderedSet = new Set(orderedIds);
|
|
321
|
+
const unordered = nodes.filter((n) => !orderedSet.has(n.id));
|
|
322
|
+
const orderedNodes = orderedIds.map((id) => nodes.find((n) => n.id === id));
|
|
323
|
+
return [...orderedNodes, ...this.autoSort(unordered)];
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
autoSort(nodes) {
|
|
327
|
+
return [...nodes].sort((a, b) => {
|
|
328
|
+
if (a.kind !== b.kind) return a.kind === "folder" ? -1 : 1;
|
|
329
|
+
const nameCmp = a.name.localeCompare(b.name);
|
|
330
|
+
if (nameCmp !== 0) return nameCmp;
|
|
331
|
+
return b.updatedAt - a.updatedAt;
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
// ── Cache management ───────────────────────────────────────────────────
|
|
335
|
+
loadChildren(parentId) {
|
|
336
|
+
return __async(this, null, function* () {
|
|
337
|
+
const nodes = yield this.adapter.getChildren(parentId, { includeTrashed: true });
|
|
338
|
+
this.hydrateCache(nodes);
|
|
339
|
+
this.cache.loadedSets.add(parentId != null ? parentId : null);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
hydrateCache(nodes) {
|
|
343
|
+
for (const node of nodes) this.cacheNode(node);
|
|
344
|
+
}
|
|
345
|
+
cacheNode(node) {
|
|
346
|
+
var _a, _b, _c, _d, _e;
|
|
347
|
+
const existing = this.cache.nodes.get(node.id);
|
|
348
|
+
if (existing) {
|
|
349
|
+
const oldKey = (_a = existing.parentId) != null ? _a : "root";
|
|
350
|
+
const newKey = (_b = node.parentId) != null ? _b : "root";
|
|
351
|
+
if (oldKey !== newKey) {
|
|
352
|
+
const oldSiblings = (_c = this.cache.childIndex.get(oldKey)) != null ? _c : [];
|
|
353
|
+
this.cache.childIndex.set(oldKey, oldSiblings.filter((id) => id !== node.id));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
this.cache.nodes.set(node.id, node);
|
|
357
|
+
const parentKey = (_d = node.parentId) != null ? _d : "root";
|
|
358
|
+
const siblings = (_e = this.cache.childIndex.get(parentKey)) != null ? _e : [];
|
|
359
|
+
if (!siblings.includes(node.id)) {
|
|
360
|
+
this.cache.childIndex.set(parentKey, [...siblings, node.id]);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
evictNode(id) {
|
|
364
|
+
var _a, _b;
|
|
365
|
+
const node = this.cache.nodes.get(id);
|
|
366
|
+
if (!node) return;
|
|
367
|
+
this.cache.nodes.delete(id);
|
|
368
|
+
const parentKey = (_a = node.parentId) != null ? _a : "root";
|
|
369
|
+
const siblings = (_b = this.cache.childIndex.get(parentKey)) != null ? _b : [];
|
|
370
|
+
this.cache.childIndex.set(parentKey, siblings.filter((s) => s !== id));
|
|
371
|
+
}
|
|
372
|
+
handleAdapterChange(change) {
|
|
373
|
+
var _a, _b, _c;
|
|
374
|
+
for (const id of change.nodeIds) {
|
|
375
|
+
const node = this.cache.nodes.get(id);
|
|
376
|
+
if (node) {
|
|
377
|
+
const parentKey = (_a = node.parentId) != null ? _a : "root";
|
|
378
|
+
const siblings = (_b = this.cache.childIndex.get(parentKey)) != null ? _b : [];
|
|
379
|
+
this.cache.childIndex.set(parentKey, siblings.filter((s) => s !== id));
|
|
380
|
+
if (node.kind === "folder") {
|
|
381
|
+
this.cache.loadedSets.delete(id);
|
|
382
|
+
}
|
|
383
|
+
this.cache.loadedSets.delete((_c = node.parentId) != null ? _c : null);
|
|
384
|
+
}
|
|
385
|
+
this.cache.nodes.delete(id);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// ── Helpers ────────────────────────────────────────────────────────────
|
|
389
|
+
resolveNode(id) {
|
|
390
|
+
return __async(this, null, function* () {
|
|
391
|
+
const node = yield this.getNode(id);
|
|
392
|
+
if (!node) throw new Error(`Node not found: ${id}`);
|
|
393
|
+
return node;
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
assertNoDuplicate(parentId, name, kind, excludeId) {
|
|
397
|
+
return __async(this, null, function* () {
|
|
398
|
+
const children = yield this.getChildren(parentId);
|
|
399
|
+
const conflict = children.find(
|
|
400
|
+
(n) => n.name === name && n.kind === kind && n.id !== excludeId && n.deletedAt === null
|
|
401
|
+
);
|
|
402
|
+
if (!conflict) return;
|
|
403
|
+
if (this.config.duplicateResolution === "throw") {
|
|
404
|
+
throw new Error(`A ${kind} named "${name}" already exists`);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
extractTargetIds(command) {
|
|
409
|
+
switch (command.op) {
|
|
410
|
+
case "create":
|
|
411
|
+
return [];
|
|
412
|
+
case "rename":
|
|
413
|
+
return [command.id];
|
|
414
|
+
case "delete":
|
|
415
|
+
case "restore":
|
|
416
|
+
case "purge":
|
|
417
|
+
case "lock":
|
|
418
|
+
case "unlock":
|
|
419
|
+
return command.ids;
|
|
420
|
+
case "move":
|
|
421
|
+
return command.ids;
|
|
422
|
+
case "write":
|
|
423
|
+
return [command.id];
|
|
424
|
+
case "reorder":
|
|
425
|
+
return [];
|
|
426
|
+
case "snapshot":
|
|
427
|
+
return [command.fileId];
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// ── Event emitter ──────────────────────────────────────────────────────
|
|
431
|
+
on(event, listener) {
|
|
432
|
+
if (!this.listeners[event]) {
|
|
433
|
+
this.listeners[event] = /* @__PURE__ */ new Set();
|
|
434
|
+
}
|
|
435
|
+
this.listeners[event].add(listener);
|
|
436
|
+
return () => this.listeners[event].delete(listener);
|
|
437
|
+
}
|
|
438
|
+
emit(event, payload) {
|
|
439
|
+
var _a;
|
|
440
|
+
(_a = this.listeners[event]) == null ? void 0 : _a.forEach((l) => l(payload));
|
|
441
|
+
}
|
|
442
|
+
emitChange(op, nodeIds) {
|
|
443
|
+
this.version++;
|
|
444
|
+
this.emit("change", { op, nodeIds, timestamp: Date.now() });
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// src/react/context/VfsProvider.tsx
|
|
449
|
+
import { useEffect, useRef, useState, useMemo } from "react";
|
|
450
|
+
import { jsx } from "react/jsx-runtime";
|
|
451
|
+
function VfsProvider({
|
|
452
|
+
children,
|
|
453
|
+
workspaces: workspaceInputs,
|
|
454
|
+
config,
|
|
455
|
+
cacheStrategy = "hybrid",
|
|
456
|
+
tabPersistence = { strategy: "session" },
|
|
457
|
+
activeWorkspaceId,
|
|
458
|
+
fallback = null
|
|
459
|
+
}) {
|
|
460
|
+
var _a, _b;
|
|
461
|
+
const resolvedConfig = useMemo(() => {
|
|
462
|
+
var _a2, _b2, _c, _d, _e, _f, _g;
|
|
463
|
+
return {
|
|
464
|
+
sessionId: config.sessionId,
|
|
465
|
+
allowDuplicateNames: (_a2 = config.allowDuplicateNames) != null ? _a2 : false,
|
|
466
|
+
duplicateResolution: (_b2 = config.duplicateResolution) != null ? _b2 : "throw",
|
|
467
|
+
checkPermission: (_c = config.checkPermission) != null ? _c : (() => true),
|
|
468
|
+
history: {
|
|
469
|
+
maxSnapshots: (_e = (_d = config.history) == null ? void 0 : _d.maxSnapshots) != null ? _e : 50,
|
|
470
|
+
autosave: (_g = (_f = config.history) == null ? void 0 : _f.autosave) != null ? _g : { enabled: false, intervalMs: 3e4 }
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
}, [config]);
|
|
474
|
+
const engineRegistry = useRef(/* @__PURE__ */ new Map());
|
|
475
|
+
const [initialised, setInitialised] = useState(/* @__PURE__ */ new Set());
|
|
476
|
+
const resolvedActiveId = (_b = activeWorkspaceId != null ? activeWorkspaceId : (_a = workspaceInputs[0]) == null ? void 0 : _a.id) != null ? _b : "";
|
|
477
|
+
useEffect(() => {
|
|
478
|
+
const registry = engineRegistry.current;
|
|
479
|
+
const incomingIds = new Set(workspaceInputs.map((w) => w.id));
|
|
480
|
+
for (const [id, engine] of registry) {
|
|
481
|
+
if (!incomingIds.has(id)) {
|
|
482
|
+
engine.destroy();
|
|
483
|
+
registry.delete(id);
|
|
484
|
+
setInitialised((prev) => {
|
|
485
|
+
const next = new Set(prev);
|
|
486
|
+
next.delete(id);
|
|
487
|
+
return next;
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
const pending = [];
|
|
492
|
+
for (const workspace of workspaceInputs) {
|
|
493
|
+
if (registry.has(workspace.id)) continue;
|
|
494
|
+
const engine = new VfsEngine(workspace.adapter, resolvedConfig);
|
|
495
|
+
registry.set(workspace.id, engine);
|
|
496
|
+
const initPromise = engine.init(cacheStrategy).then(() => {
|
|
497
|
+
setInitialised((prev) => new Set(prev).add(workspace.id));
|
|
498
|
+
});
|
|
499
|
+
pending.push(initPromise);
|
|
500
|
+
}
|
|
501
|
+
Promise.allSettled(pending);
|
|
502
|
+
return () => {
|
|
503
|
+
for (const engine of registry.values()) engine.destroy();
|
|
504
|
+
registry.clear();
|
|
505
|
+
};
|
|
506
|
+
}, [workspaceInputs, cacheStrategy]);
|
|
507
|
+
const contextValue = useMemo(() => {
|
|
508
|
+
const workspaces = /* @__PURE__ */ new Map();
|
|
509
|
+
for (const input of workspaceInputs) {
|
|
510
|
+
const engine = engineRegistry.current.get(input.id);
|
|
511
|
+
if (!engine) continue;
|
|
512
|
+
workspaces.set(input.id, { id: input.id, name: input.name, engine });
|
|
513
|
+
}
|
|
514
|
+
return {
|
|
515
|
+
workspaces,
|
|
516
|
+
activeWorkspaceId: resolvedActiveId,
|
|
517
|
+
config: resolvedConfig,
|
|
518
|
+
tabPersistence,
|
|
519
|
+
isReady: (id) => initialised.has(id)
|
|
520
|
+
};
|
|
521
|
+
}, [initialised, workspaceInputs, resolvedActiveId, resolvedConfig, tabPersistence]);
|
|
522
|
+
const allReady = workspaceInputs.every((w) => initialised.has(w.id));
|
|
523
|
+
return /* @__PURE__ */ jsx(VfsContext.Provider, { value: contextValue, children: allReady ? children : fallback });
|
|
524
|
+
}
|
|
525
|
+
export {
|
|
526
|
+
VfsAdapter,
|
|
527
|
+
VfsEngine,
|
|
528
|
+
VfsProvider
|
|
529
|
+
};
|
|
530
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/VfsEngine.ts","../src/react/context/VfsProvider.tsx"],"sourcesContent":["import { VfsAdapter } from \"./VfsAdapter\";\nimport {\n VfsNode, VfsFileNode, VfsFolderNode,\n VfsNodeOrder, VfsFileSnapshot,\n VfsOperation, VfsEngineConfig\n} from \"./VfsNode\";\nimport { VfsChange } from \"./VfsAdapter\";\n\n// ── Commands & Events ────────────────────────────────────────────────────\n\nexport type VfsCommand =\n | { op: \"create\"; kind: \"file\"; parentId: string | null; name: string; mimeType?: string; meta?: Record<string, unknown> }\n | { op: \"create\"; kind: \"folder\"; parentId: string | null; name: string; meta?: Record<string, unknown> }\n | { op: \"rename\"; id: string; newName: string }\n | { op: \"delete\"; ids: string[]; permanent?: boolean }\n | { op: \"restore\"; ids: string[] }\n | { op: \"purge\"; ids: string[] }\n | { op: \"move\"; ids: string[]; newParentId: string | null }\n | { op: \"write\"; id: string; content: Uint8Array }\n | { op: \"lock\"; ids: string[] }\n | { op: \"unlock\"; ids: string[] }\n | { op: \"reorder\"; parentId: string | null; orderedIds: string[] }\n | { op: \"snapshot\"; fileId: string; label?: string }\n\nexport type VfsEventMap<TMeta> = {\n created: VfsNode<TMeta>;\n renamed: VfsNode<TMeta>;\n deleted: { ids: string[]; permanent: boolean };\n restored: VfsNode<TMeta>[];\n purged: { ids: string[] };\n moved: VfsNode<TMeta>[];\n written: { id: string };\n locked: VfsNode<TMeta>[];\n unlocked: VfsNode<TMeta>[];\n reordered: { parentId: string | null; orderedIds: string[] };\n warning: { code: string; tabId?: string; nodeId?: string };\n snapshot: VfsFileSnapshot;\n\n pending: { command: VfsCommand };\n settled: { command: VfsCommand; success: boolean };\n error: { command: VfsCommand; error: Error };\n\n change: VfsChange;\n};\n\ntype VfsEventListener<TMeta, K extends keyof VfsEventMap<TMeta>> =\n (payload: VfsEventMap<TMeta>[K]) => void;\n\n// ── Cache model ────────────────────────────────────────────────────────────\n\ntype CacheStrategy = \"eager\" | \"lazy\" | \"hybrid\";\ninterface VfsCache<TMeta> {\n nodes: Map<string, VfsNode<TMeta>>;\n childIndex: Map<string, string[]>;\n loadedSets: Set<string | null>;\n}\n\n// ── Engine ─────────────────────────────────────────────────────────────────\n\nexport class VfsEngine<TMeta extends Record<string, unknown> | undefined = Record<string, unknown>> {\n private adapter: VfsAdapter<TMeta>;\n private config: Required<VfsEngineConfig<TMeta>>;\n private cache: VfsCache<TMeta>;\n private listeners: { [K in keyof VfsEventMap<TMeta>]?: Set<VfsEventListener<TMeta, K>> };\n private unsubscribeAdapter: (() => void) | null = null;\n\n public version: number = 0;\n\n public pending: boolean = false;\n public lastError: Error | null = null;\n\n constructor(adapter: VfsAdapter<TMeta>, config: Partial<VfsEngineConfig<TMeta>> & { sessionId: string }) {\n this.adapter = adapter;\n this.config = {\n sessionId: config.sessionId,\n allowDuplicateNames: config.allowDuplicateNames ?? false,\n duplicateResolution: config.duplicateResolution ?? \"throw\",\n checkPermission: config.checkPermission ?? (() => true),\n history: {\n maxSnapshots: config.history?.maxSnapshots ?? 50,\n autosave: config.history?.autosave ?? { enabled: false, intervalMs: 30_000 },\n },\n };\n this.cache = {\n nodes: new Map(),\n childIndex: new Map(),\n loadedSets: new Set(),\n };\n this.listeners = {};\n }\n\n async init(strategy: CacheStrategy = \"hybrid\"): Promise<void> {\n this.unsubscribeAdapter = this.adapter.onChanged((change) => {\n this.handleAdapterChange(change);\n });\n\n if (strategy === \"eager\") {\n const all = await this.adapter.getNodes([]);\n this.hydrateCache(all);\n } else if (strategy === \"hybrid\") {\n await this.loadChildren(null);\n }\n }\n\n destroy(): void {\n this.unsubscribeAdapter?.();\n this.cache.nodes.clear();\n this.cache.childIndex.clear();\n this.cache.loadedSets.clear();\n }\n\n subscribe(onStoreChange: () => void): () => void {\n return this.on(\"change\", onStoreChange);\n }\n\n // ── Command entry point ────────────────────────────────────────────────\n\n async execute(command: VfsCommand): Promise<void> {\n const targetIds = this.extractTargetIds(command);\n for (const id of targetIds) {\n const node = this.cache.nodes.get(id);\n if (node && !this.config.checkPermission(node, command.op as VfsOperation)) {\n throw new Error(`Permission denied: ${command.op} on node ${id}`);\n }\n }\n\n this.pending = true;\n this.lastError = null;\n this.emit(\"pending\", { command });\n\n try {\n await this.runCommand(command);\n this.pending = false;\n this.emit(\"settled\", { command, success: true });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n this.lastError = error;\n this.pending = false;\n this.emit(\"error\", { command, error });\n this.emit(\"settled\", { command, success: false });\n throw error;\n }\n }\n\n // ── Command router ─────────────────────────────────────────────────────\n\n private async runCommand(command: VfsCommand): Promise<void> {\n switch (command.op) {\n case \"create\": return this.runCreate(command);\n case \"rename\": return this.runRename(command);\n case \"delete\": return this.runDelete(command);\n case \"restore\": return this.runRestore(command);\n case \"purge\": return this.runPurge(command);\n case \"move\": return this.runMove(command);\n case \"write\": return this.runWrite(command);\n case \"lock\": return this.runLock(command);\n case \"unlock\": return this.runUnlock(command);\n case \"reorder\": return this.runReorder(command);\n case \"snapshot\": return this.runSnapshot(command);\n }\n }\n\n // ── Command implementations ────────────────────────────────────────────\n\n private async runCreate(command: Extract<VfsCommand, { op: \"create\" }>): Promise<void> {\n if (!this.config.allowDuplicateNames) {\n await this.assertNoDuplicate(command.parentId, command.name, command.kind);\n }\n\n const node = command.kind === \"file\"\n ? await this.adapter.createFile({\n parentId: command.parentId,\n name: command.name,\n mimeType: command.mimeType,\n meta: command.meta as TMeta,\n })\n : await this.adapter.createFolder({\n parentId: command.parentId,\n name: command.name,\n meta: command.meta as TMeta,\n });\n\n this.cacheNode(node);\n this.emit(\"created\", node);\n this.emitChange(\"create\", [node.id]);\n }\n\n private async runRename(command: Extract<VfsCommand, { op: \"rename\" }>): Promise<void> {\n const existing = await this.resolveNode(command.id);\n if (!this.config.allowDuplicateNames) {\n await this.assertNoDuplicate(existing.parentId, command.newName, existing.kind, command.id);\n }\n\n const updated = await this.adapter.updateNode(command.id, { name: command.newName } as Partial<VfsNode<TMeta>>);\n this.cacheNode(updated);\n this.emit(\"renamed\", updated);\n this.emitChange(\"rename\", [command.id]);\n }\n\n private async runDelete(command: Extract<VfsCommand, { op: \"delete\" }>): Promise<void> {\n await this.adapter.deleteNodes(command.ids, command.permanent);\n for (const id of command.ids) this.evictNode(id);\n this.emit(\"deleted\", { ids: command.ids, permanent: command.permanent ?? false });\n this.emitChange(\"delete\", command.ids);\n }\n\n private async runRestore(command: Extract<VfsCommand, { op: \"restore\" }>): Promise<void> {\n const restored = await Promise.all(command.ids.map(id => this.adapter.restoreNode(id)));\n for (const node of restored) this.cacheNode(node);\n this.emit(\"restored\", restored);\n this.emitChange(\"restore\", command.ids);\n }\n\n private async runPurge(command: Extract<VfsCommand, { op: \"purge\" }>): Promise<void> {\n await Promise.all(command.ids.map(id => this.adapter.purgeNode(id)));\n for (const id of command.ids) this.evictNode(id);\n this.emit(\"purged\", { ids: command.ids });\n this.emitChange(\"purge\", command.ids);\n }\n\n private async runMove(command: Extract<VfsCommand, { op: \"move\" }>): Promise<void> {\n // 1. Capture old parentIds from cache BEFORE anything mutates\n const oldParentIds = new Map(\n command.ids.map(id => [id, this.cache.nodes.get(id)?.parentId ?? null])\n );\n\n await this.adapter.moveNodes(command.ids, command.newParentId);\n\n // 2. Manually fix the cache using the captured old parentIds\n for (const id of command.ids) {\n const oldParentKey = oldParentIds.get(id) ?? null;\n\n // Remove from old parent's child list\n const oldSiblings = this.cache.childIndex.get(oldParentKey ?? \"root\") ?? [];\n this.cache.childIndex.set(\n oldParentKey ?? \"root\",\n oldSiblings.filter(s => s !== id)\n );\n\n // Fetch fresh node (with updated parentId) and cache it\n const updated = await this.adapter.getNodeById(id);\n if (!updated) continue;\n this.cache.nodes.set(updated.id, updated);\n\n // Add to new parent's child list\n const newParentKey = updated.parentId ?? \"root\";\n const newSiblings = this.cache.childIndex.get(newParentKey) ?? [];\n if (!newSiblings.includes(id)) {\n this.cache.childIndex.set(newParentKey, [...newSiblings, id]);\n }\n }\n\n const movedNodes = command.ids\n .map(id => this.cache.nodes.get(id))\n .filter((n): n is VfsNode<TMeta> => !!n);\n\n this.emit(\"moved\", movedNodes);\n this.emitChange(\"move\", command.ids);\n }\n\n private async runWrite(command: Extract<VfsCommand, { op: \"write\" }>): Promise<void> {\n const node = await this.resolveNode(command.id);\n if (node.kind !== \"file\") throw new Error(`Cannot write to folder: ${command.id}`);\n if (node.lockedBy && node.lockedBy !== this.config.sessionId) {\n throw new Error(`File ${command.id} is locked by another session`);\n }\n await this.adapter.writeFile(command.id, command.content);\n\n const updated = await this.adapter.updateNode(command.id, {\n size: command.content.byteLength,\n updatedAt: Date.now(),\n } as Partial<VfsNode<TMeta>>);\n this.cacheNode(updated);\n this.emit(\"written\", { id: command.id });\n this.emitChange(\"write\", [command.id]);\n }\n\n private async runLock(command: Extract<VfsCommand, { op: \"lock\" }>): Promise<void> {\n const locked = await Promise.all(\n command.ids.map(id => this.adapter.lockNode(id, this.config.sessionId))\n );\n for (const node of locked) this.cacheNode(node);\n this.emit(\"locked\", locked);\n this.emitChange(\"lock\", command.ids);\n }\n\n private async runUnlock(command: Extract<VfsCommand, { op: \"unlock\" }>): Promise<void> {\n const unlocked = await Promise.all(\n command.ids.map(id => this.adapter.unlockNode(id, this.config.sessionId))\n );\n for (const node of unlocked) this.cacheNode(node);\n this.emit(\"unlocked\", unlocked);\n this.emitChange(\"unlock\", command.ids);\n }\n\n private async runReorder(command: Extract<VfsCommand, { op: \"reorder\" }>): Promise<void> {\n await this.adapter.setOrder(command.parentId, command.orderedIds);\n this.emit(\"reordered\", { parentId: command.parentId, orderedIds: command.orderedIds });\n this.emitChange(\"reorder\", []);\n }\n\n private async runSnapshot(command: Extract<VfsCommand, { op: \"snapshot\" }>): Promise<void> {\n if (!this.adapter.supportsHistory) {\n throw new Error(\"Adapter does not support history\");\n }\n const content = await this.adapter.readFile(command.fileId);\n const snapshot = await this.adapter.saveSnapshot(command.fileId, content, command.label);\n this.emit(\"snapshot\", snapshot);\n this.emitChange(\"snapshot\", [command.fileId]);\n }\n\n // ── Read API ───────────────────────────────────────────────────────────\n\n async getNode(id: string): Promise<VfsNode<TMeta> | null> {\n if (this.cache.nodes.has(id)) return this.cache.nodes.get(id)!;\n const node = await this.adapter.getNodeById(id);\n if (node) this.cacheNode(node);\n return node;\n }\n\n async getChildren(parentId: string | null, options?: { includeTrashed?: boolean }): Promise<VfsNode<TMeta>[]> {\n if (!this.cache.loadedSets.has(parentId ?? null)) {\n await this.loadChildren(parentId);\n }\n const ids = this.cache.childIndex.get(parentId ?? \"root\") ?? [];\n const nodes = ids\n .map(id => this.cache.nodes.get(id))\n .filter((n): n is VfsNode<TMeta> => !!n)\n .filter(n => options?.includeTrashed ? true : n.deletedAt === null);\n\n return this.applySortOrder(nodes, parentId);\n }\n\n async getPath(id: string): Promise<string> {\n const parts: string[] = [];\n let current: VfsNode<TMeta> | null = await this.getNode(id);\n while (current) {\n parts.unshift(current.name);\n current = current.parentId ? await this.getNode(current.parentId) : null;\n }\n return \"/\" + parts.join(\"/\");\n }\n\n async readFile(id: string): Promise<Uint8Array> {\n return this.adapter.readFile(id);\n }\n\n async search(query: string, options?: { scope?: string | null; kind?: \"file\" | \"folder\"; includeTrashed?: boolean }): Promise<VfsNode<TMeta>[]> {\n return this.adapter.search(query, options);\n }\n\n async getTrashed(): Promise<VfsNode<TMeta>[]> {\n return this.adapter.getTrashed();\n }\n\n async getSnapshots(fileId: string): Promise<VfsFileSnapshot[]> {\n if (!this.adapter.supportsHistory) throw new Error(\"Adapter does not support history\");\n return this.adapter.getSnapshots(fileId);\n }\n\n // ── Sort resolution ────────────────────────────────────────────────────\n\n private async applySortOrder(nodes: VfsNode<TMeta>[], parentId: string | null): Promise<VfsNode<TMeta>[]> {\n const order = await this.adapter.getOrder(parentId);\n if (!order) return this.autoSort(nodes);\n\n const validIds = new Set(nodes.map(n => n.id));\n const orderedIds = order.orderedIds.filter(id => validIds.has(id));\n const orderedSet = new Set(orderedIds);\n const unordered = nodes.filter(n => !orderedSet.has(n.id));\n\n const orderedNodes = orderedIds.map(id => nodes.find(n => n.id === id)!);\n return [...orderedNodes, ...this.autoSort(unordered)];\n }\n\n private autoSort(nodes: VfsNode<TMeta>[]): VfsNode<TMeta>[] {\n return [...nodes].sort((a, b) => {\n if (a.kind !== b.kind) return a.kind === \"folder\" ? -1 : 1;\n\n const nameCmp = a.name.localeCompare(b.name);\n if (nameCmp !== 0) return nameCmp;\n\n return b.updatedAt - a.updatedAt;\n });\n }\n\n // ── Cache management ───────────────────────────────────────────────────\n\n private async loadChildren(parentId: string | null): Promise<void> {\n const nodes = await this.adapter.getChildren(parentId, { includeTrashed: true });\n this.hydrateCache(nodes);\n this.cache.loadedSets.add(parentId ?? null);\n }\n \n private hydrateCache(nodes: VfsNode<TMeta>[]): void {\n for (const node of nodes) this.cacheNode(node);\n }\n\n private cacheNode(node: VfsNode<TMeta>): void {\n const existing = this.cache.nodes.get(node.id);\n \n if (existing) {\n const oldKey = existing.parentId ?? \"root\";\n const newKey = node.parentId ?? \"root\";\n if (oldKey !== newKey) {\n const oldSiblings = this.cache.childIndex.get(oldKey) ?? [];\n this.cache.childIndex.set(oldKey, oldSiblings.filter(id => id !== node.id));\n }\n }\n \n this.cache.nodes.set(node.id, node);\n \n const parentKey = node.parentId ?? \"root\";\n const siblings = this.cache.childIndex.get(parentKey) ?? [];\n if (!siblings.includes(node.id)) {\n this.cache.childIndex.set(parentKey, [...siblings, node.id]);\n }\n }\n\n private evictNode(id: string): void {\n const node = this.cache.nodes.get(id);\n if (!node) return;\n this.cache.nodes.delete(id);\n const parentKey = node.parentId ?? \"root\";\n const siblings = this.cache.childIndex.get(parentKey) ?? [];\n this.cache.childIndex.set(parentKey, siblings.filter(s => s !== id));\n }\n\n private handleAdapterChange(change: VfsChange): void {\n for (const id of change.nodeIds) {\n const node = this.cache.nodes.get(id);\n if (node) {\n const parentKey = node.parentId ?? \"root\";\n const siblings = this.cache.childIndex.get(parentKey) ?? [];\n this.cache.childIndex.set(parentKey, siblings.filter(s => s !== id));\n \n if (node.kind === \"folder\") {\n this.cache.loadedSets.delete(id);\n }\n \n this.cache.loadedSets.delete(node.parentId ?? null);\n }\n \n this.cache.nodes.delete(id);\n }\n }\n\n // ── Helpers ────────────────────────────────────────────────────────────\n\n private async resolveNode(id: string): Promise<VfsNode<TMeta>> {\n const node = await this.getNode(id);\n if (!node) throw new Error(`Node not found: ${id}`);\n return node;\n }\n\n private async assertNoDuplicate(\n parentId: string | null,\n name: string,\n kind: \"file\" | \"folder\",\n excludeId?: string\n ): Promise<void> {\n const children = await this.getChildren(parentId);\n const conflict = children.find(n =>\n n.name === name &&\n n.kind === kind &&\n n.id !== excludeId &&\n n.deletedAt === null\n );\n if (!conflict) return;\n if (this.config.duplicateResolution === \"throw\") {\n throw new Error(`A ${kind} named \"${name}\" already exists`);\n }\n }\n\n private extractTargetIds(command: VfsCommand): string[] {\n switch (command.op) {\n case \"create\": return [];\n case \"rename\": return [command.id];\n case \"delete\":\n case \"restore\":\n case \"purge\":\n case \"lock\":\n case \"unlock\": return command.ids;\n case \"move\": return command.ids;\n case \"write\": return [command.id];\n case \"reorder\": return [];\n case \"snapshot\": return [command.fileId];\n }\n }\n\n // ── Event emitter ──────────────────────────────────────────────────────\n\n on<K extends keyof VfsEventMap<TMeta>>(event: K, listener: VfsEventListener<TMeta, K>): () => void {\n if (!this.listeners[event]) {\n this.listeners[event] = new Set() as any;\n }\n (this.listeners[event] as Set<VfsEventListener<TMeta, K>>).add(listener);\n return () => (this.listeners[event] as Set<VfsEventListener<TMeta, K>>).delete(listener);\n }\n\n public emit<K extends keyof VfsEventMap<TMeta>>(\n event: K, \n payload: VfsEventMap<TMeta>[K]\n ): void {\n (this.listeners[event] as Set<VfsEventListener<TMeta, K>> | undefined)\n ?.forEach(l => l(payload));\n }\n\n private emitChange(op: VfsOperation, nodeIds: string[]): void {\n this.version++;\n this.emit(\"change\", { op, nodeIds, timestamp: Date.now() });\n }\n}","import { useEffect, useRef, useState, useMemo, ReactNode, useReducer } from \"react\";\nimport { VfsContext, VfsTabPersistenceConfig, VfsWorkspaceEntry } from \"./VfsContext\";\nimport { VfsAdapter } from \"../../core/VfsAdapter\";\nimport { VfsEngine } from \"../../core/VfsEngine\";\nimport { VfsEngineConfig } from \"../../core/VfsNode\";\n\nexport interface VfsWorkspaceInput<TMeta = Record<string, unknown>> {\n id: string;\n name: string;\n adapter: VfsAdapter<TMeta>;\n}\n\nexport interface VfsProviderProps<TMeta = Record<string, unknown>> {\n children: ReactNode;\n workspaces: VfsWorkspaceInput<TMeta>[];\n config: Omit<VfsEngineConfig<TMeta>, \"allowDuplicateNames\" | \"duplicateResolution\"> &\n Partial<Pick<VfsEngineConfig<TMeta>, \"allowDuplicateNames\" | \"duplicateResolution\">>;\n cacheStrategy?: \"eager\" | \"lazy\" | \"hybrid\";\n tabPersistence?: VfsTabPersistenceConfig;\n activeWorkspaceId?: string;\n fallback?: ReactNode;\n}\n\nexport function VfsProvider<TMeta extends Record<string, unknown> | undefined = Record<string, unknown>>({\n children,\n workspaces: workspaceInputs,\n config,\n cacheStrategy = \"hybrid\",\n tabPersistence = { strategy: \"session\" },\n activeWorkspaceId,\n fallback = null, \n}: VfsProviderProps<TMeta>) {\n const resolvedConfig = useMemo<Required<VfsEngineConfig<TMeta>>>(() => ({\n sessionId: config.sessionId,\n allowDuplicateNames: config.allowDuplicateNames ?? false,\n duplicateResolution: config.duplicateResolution ?? \"throw\",\n checkPermission: config.checkPermission ?? (() => true),\n history: {\n maxSnapshots: config.history?.maxSnapshots ?? 50,\n autosave: config.history?.autosave ?? { enabled: false, intervalMs: 30_000 },\n },\n }), [config]);\n\n const engineRegistry = useRef<Map<string, VfsEngine<TMeta>>>(new Map());\n const [initialised, setInitialised] = useState<Set<string>>(new Set());\n const resolvedActiveId = activeWorkspaceId ?? workspaceInputs[0]?.id ?? \"\";\n\n useEffect(() => {\n const registry = engineRegistry.current;\n const incomingIds = new Set(workspaceInputs.map(w => w.id));\n\n for (const [id, engine] of registry) {\n if (!incomingIds.has(id)) {\n engine.destroy();\n registry.delete(id);\n setInitialised(prev => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n }\n }\n\n const pending: Promise<void>[] = [];\n\n for (const workspace of workspaceInputs) {\n if (registry.has(workspace.id)) continue;\n\n const engine = new VfsEngine<TMeta>(workspace.adapter, resolvedConfig);\n registry.set(workspace.id, engine);\n\n const initPromise = engine.init(cacheStrategy).then(() => {\n setInitialised(prev => new Set(prev).add(workspace.id));\n });\n\n pending.push(initPromise);\n }\n\n Promise.allSettled(pending);\n\n return () => {\n for (const engine of registry.values()) engine.destroy();\n registry.clear();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [workspaceInputs, cacheStrategy]);\n\n // ── Build context value ────────────────────────────────────────────────\n\n const contextValue = useMemo(() => {\n const workspaces = new Map<string, VfsWorkspaceEntry<TMeta>>();\n\n for (const input of workspaceInputs) {\n const engine = engineRegistry.current.get(input.id);\n if (!engine) continue;\n workspaces.set(input.id, { id: input.id, name: input.name, engine });\n }\n\n return {\n workspaces,\n activeWorkspaceId: resolvedActiveId,\n config: resolvedConfig,\n tabPersistence,\n isReady: (id: string) => initialised.has(id),\n };\n }, [initialised, workspaceInputs, resolvedActiveId, resolvedConfig, tabPersistence]);\n\n const allReady = workspaceInputs.every(w => initialised.has(w.id));\n\n return (\n <VfsContext.Provider value={contextValue}>\n {allReady ? children : fallback}\n </VfsContext.Provider>\n );\n}"],"mappings":";;;;;;;;;;;;AA2DO,IAAM,YAAN,MAA6F;AAAA,EAYhG,YAAY,SAA4B,QAAiE;AAPzG,SAAQ,qBAA0C;AAElD,SAAO,UAAkB;AAEzB,SAAO,UAAqB;AAC5B,SAAO,YAA0B;AArErC;AAwEQ,SAAK,UAAU;AACf,SAAK,SAAS;AAAA,MACV,WAAoB,OAAO;AAAA,MAC3B,sBAAqB,YAAO,wBAAP,YAA8B;AAAA,MACnD,sBAAqB,YAAO,wBAAP,YAA8B;AAAA,MACnD,kBAAoB,YAAO,oBAAP,aAA2B,MAAM;AAAA,MACrD,SAAS;AAAA,QACL,eAAc,kBAAO,YAAP,mBAAgB,iBAAhB,YAAgC;AAAA,QAC9C,WAAU,kBAAO,YAAP,mBAAgB,aAAhB,YAA4B,EAAE,SAAS,OAAO,YAAY,IAAO;AAAA,MAC/E;AAAA,IACJ;AACA,SAAK,QAAQ;AAAA,MACT,OAAY,oBAAI,IAAI;AAAA,MACpB,YAAY,oBAAI,IAAI;AAAA,MACpB,YAAY,oBAAI,IAAI;AAAA,IACxB;AACA,SAAK,YAAY,CAAC;AAAA,EACtB;AAAA,EAEM,KAAK,WAA0B,UAAyB;AAAA;AAC1D,WAAK,qBAAqB,KAAK,QAAQ,UAAU,CAAC,WAAW;AACzD,aAAK,oBAAoB,MAAM;AAAA,MACnC,CAAC;AAED,UAAI,aAAa,SAAS;AACtB,cAAM,MAAM,MAAM,KAAK,QAAQ,SAAS,CAAC,CAAC;AAC1C,aAAK,aAAa,GAAG;AAAA,MACzB,WAAW,aAAa,UAAU;AAC9B,cAAM,KAAK,aAAa,IAAI;AAAA,MAChC;AAAA,IACJ;AAAA;AAAA,EAEA,UAAgB;AAxGpB;AAyGQ,eAAK,uBAAL;AACA,SAAK,MAAM,MAAM,MAAM;AACvB,SAAK,MAAM,WAAW,MAAM;AAC5B,SAAK,MAAM,WAAW,MAAM;AAAA,EAChC;AAAA,EAEA,UAAU,eAAuC;AAC7C,WAAO,KAAK,GAAG,UAAU,aAAa;AAAA,EAC1C;AAAA;AAAA,EAIM,QAAQ,SAAoC;AAAA;AAC9C,YAAM,YAAY,KAAK,iBAAiB,OAAO;AAC/C,iBAAW,MAAM,WAAW;AACxB,cAAM,OAAO,KAAK,MAAM,MAAM,IAAI,EAAE;AACpC,YAAI,QAAQ,CAAC,KAAK,OAAO,gBAAgB,MAAM,QAAQ,EAAkB,GAAG;AACxE,gBAAM,IAAI,MAAM,sBAAsB,QAAQ,EAAE,YAAY,EAAE,EAAE;AAAA,QACpE;AAAA,MACJ;AAEA,WAAK,UAAY;AACjB,WAAK,YAAY;AACjB,WAAK,KAAK,WAAW,EAAE,QAAQ,CAAC;AAEhC,UAAI;AACA,cAAM,KAAK,WAAW,OAAO;AAC7B,aAAK,UAAU;AACf,aAAK,KAAK,WAAW,EAAE,SAAS,SAAS,KAAK,CAAC;AAAA,MACnD,SAAS,KAAK;AACV,cAAM,QAAW,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AACnE,aAAK,YAAY;AACjB,aAAK,UAAY;AACjB,aAAK,KAAK,SAAW,EAAE,SAAS,MAAM,CAAC;AACvC,aAAK,KAAK,WAAW,EAAE,SAAS,SAAS,MAAM,CAAC;AAChD,cAAM;AAAA,MACV;AAAA,IACJ;AAAA;AAAA;AAAA,EAIc,WAAW,SAAoC;AAAA;AACzD,cAAQ,QAAQ,IAAI;AAAA,QAChB,KAAK;AAAY,iBAAO,KAAK,UAAU,OAAO;AAAA,QAC9C,KAAK;AAAY,iBAAO,KAAK,UAAU,OAAO;AAAA,QAC9C,KAAK;AAAY,iBAAO,KAAK,UAAU,OAAO;AAAA,QAC9C,KAAK;AAAY,iBAAO,KAAK,WAAW,OAAO;AAAA,QAC/C,KAAK;AAAY,iBAAO,KAAK,SAAS,OAAO;AAAA,QAC7C,KAAK;AAAY,iBAAO,KAAK,QAAQ,OAAO;AAAA,QAC5C,KAAK;AAAY,iBAAO,KAAK,SAAS,OAAO;AAAA,QAC7C,KAAK;AAAY,iBAAO,KAAK,QAAQ,OAAO;AAAA,QAC5C,KAAK;AAAY,iBAAO,KAAK,UAAU,OAAO;AAAA,QAC9C,KAAK;AAAY,iBAAO,KAAK,WAAW,OAAO;AAAA,QAC/C,KAAK;AAAY,iBAAO,KAAK,YAAY,OAAO;AAAA,MACpD;AAAA,IACJ;AAAA;AAAA;AAAA,EAIc,UAAU,SAA+D;AAAA;AACnF,UAAI,CAAC,KAAK,OAAO,qBAAqB;AAClC,cAAM,KAAK,kBAAkB,QAAQ,UAAU,QAAQ,MAAM,QAAQ,IAAI;AAAA,MAC7E;AAEA,YAAM,OAAO,QAAQ,SAAS,SACxB,MAAM,KAAK,QAAQ,WAAW;AAAA,QAC5B,UAAU,QAAQ;AAAA,QAClB,MAAU,QAAQ;AAAA,QAClB,UAAU,QAAQ;AAAA,QAClB,MAAU,QAAQ;AAAA,MACpB,CAAC,IACD,MAAM,KAAK,QAAQ,aAAa;AAAA,QAC9B,UAAU,QAAQ;AAAA,QAClB,MAAU,QAAQ;AAAA,QAClB,MAAU,QAAQ;AAAA,MACpB,CAAC;AAEP,WAAK,UAAU,IAAI;AACnB,WAAK,KAAK,WAAW,IAAI;AACzB,WAAK,WAAW,UAAU,CAAC,KAAK,EAAE,CAAC;AAAA,IACvC;AAAA;AAAA,EAEc,UAAU,SAA+D;AAAA;AACnF,YAAM,WAAW,MAAM,KAAK,YAAY,QAAQ,EAAE;AAClD,UAAI,CAAC,KAAK,OAAO,qBAAqB;AAClC,cAAM,KAAK,kBAAkB,SAAS,UAAU,QAAQ,SAAS,SAAS,MAAM,QAAQ,EAAE;AAAA,MAC9F;AAEA,YAAM,UAAU,MAAM,KAAK,QAAQ,WAAW,QAAQ,IAAI,EAAE,MAAM,QAAQ,QAAQ,CAA4B;AAC9G,WAAK,UAAU,OAAO;AACtB,WAAK,KAAK,WAAW,OAAO;AAC5B,WAAK,WAAW,UAAU,CAAC,QAAQ,EAAE,CAAC;AAAA,IAC1C;AAAA;AAAA,EAEc,UAAU,SAA+D;AAAA;AAvM3F;AAwMQ,YAAM,KAAK,QAAQ,YAAY,QAAQ,KAAK,QAAQ,SAAS;AAC7D,iBAAW,MAAM,QAAQ,IAAK,MAAK,UAAU,EAAE;AAC/C,WAAK,KAAK,WAAW,EAAE,KAAK,QAAQ,KAAK,YAAW,aAAQ,cAAR,YAAqB,MAAM,CAAC;AAChF,WAAK,WAAW,UAAU,QAAQ,GAAG;AAAA,IACzC;AAAA;AAAA,EAEc,WAAW,SAAgE;AAAA;AACrF,YAAM,WAAW,MAAM,QAAQ,IAAI,QAAQ,IAAI,IAAI,QAAM,KAAK,QAAQ,YAAY,EAAE,CAAC,CAAC;AACtF,iBAAW,QAAQ,SAAU,MAAK,UAAU,IAAI;AAChD,WAAK,KAAK,YAAY,QAAQ;AAC9B,WAAK,WAAW,WAAW,QAAQ,GAAG;AAAA,IAC1C;AAAA;AAAA,EAEc,SAAS,SAA8D;AAAA;AACjF,YAAM,QAAQ,IAAI,QAAQ,IAAI,IAAI,QAAM,KAAK,QAAQ,UAAU,EAAE,CAAC,CAAC;AACnE,iBAAW,MAAM,QAAQ,IAAK,MAAK,UAAU,EAAE;AAC/C,WAAK,KAAK,UAAU,EAAE,KAAK,QAAQ,IAAI,CAAC;AACxC,WAAK,WAAW,SAAS,QAAQ,GAAG;AAAA,IACxC;AAAA;AAAA,EAEc,QAAQ,SAA6D;AAAA;AA5NvF;AA8NQ,YAAM,eAAe,IAAI;AAAA,QACrB,QAAQ,IAAI,IAAI,QAAG;AA/N/B,cAAAA,KAAAC;AA+NkC,kBAAC,KAAIA,OAAAD,MAAA,KAAK,MAAM,MAAM,IAAI,EAAE,MAAvB,gBAAAA,IAA0B,aAA1B,OAAAC,MAAsC,IAAI;AAAA,SAAC;AAAA,MAC1E;AAEA,YAAM,KAAK,QAAQ,UAAU,QAAQ,KAAK,QAAQ,WAAW;AAG7D,iBAAW,MAAM,QAAQ,KAAK;AAC1B,cAAM,gBAAe,kBAAa,IAAI,EAAE,MAAnB,YAAwB;AAG7C,cAAM,eAAc,UAAK,MAAM,WAAW,IAAI,sCAAgB,MAAM,MAAhD,YAAqD,CAAC;AAC1E,aAAK,MAAM,WAAW;AAAA,UAClB,sCAAgB;AAAA,UAChB,YAAY,OAAO,OAAK,MAAM,EAAE;AAAA,QACpC;AAGA,cAAM,UAAU,MAAM,KAAK,QAAQ,YAAY,EAAE;AACjD,YAAI,CAAC,QAAS;AACd,aAAK,MAAM,MAAM,IAAI,QAAQ,IAAI,OAAO;AAGxC,cAAM,gBAAe,aAAQ,aAAR,YAAoB;AACzC,cAAM,eAAe,UAAK,MAAM,WAAW,IAAI,YAAY,MAAtC,YAA2C,CAAC;AACjE,YAAI,CAAC,YAAY,SAAS,EAAE,GAAG;AAC3B,eAAK,MAAM,WAAW,IAAI,cAAc,CAAC,GAAG,aAAa,EAAE,CAAC;AAAA,QAChE;AAAA,MACJ;AAEA,YAAM,aAAa,QAAQ,IACtB,IAAI,QAAM,KAAK,MAAM,MAAM,IAAI,EAAE,CAAC,EAClC,OAAO,CAAC,MAA2B,CAAC,CAAC,CAAC;AAE3C,WAAK,KAAK,SAAS,UAAU;AAC7B,WAAK,WAAW,QAAQ,QAAQ,GAAG;AAAA,IACvC;AAAA;AAAA,EAEc,SAAS,SAA8D;AAAA;AACjF,YAAM,OAAO,MAAM,KAAK,YAAY,QAAQ,EAAE;AAC9C,UAAI,KAAK,SAAS,OAAQ,OAAM,IAAI,MAAM,2BAA2B,QAAQ,EAAE,EAAE;AACjF,UAAI,KAAK,YAAY,KAAK,aAAa,KAAK,OAAO,WAAW;AAC1D,cAAM,IAAI,MAAM,QAAQ,QAAQ,EAAE,+BAA+B;AAAA,MACrE;AACA,YAAM,KAAK,QAAQ,UAAU,QAAQ,IAAI,QAAQ,OAAO;AAExD,YAAM,UAAU,MAAM,KAAK,QAAQ,WAAW,QAAQ,IAAI;AAAA,QACtD,MAAM,QAAQ,QAAQ;AAAA,QACtB,WAAW,KAAK,IAAI;AAAA,MACxB,CAA4B;AAC5B,WAAK,UAAU,OAAO;AACtB,WAAK,KAAK,WAAW,EAAE,IAAI,QAAQ,GAAG,CAAC;AACvC,WAAK,WAAW,SAAS,CAAC,QAAQ,EAAE,CAAC;AAAA,IACzC;AAAA;AAAA,EAEc,QAAQ,SAA6D;AAAA;AAC/E,YAAM,SAAS,MAAM,QAAQ;AAAA,QACzB,QAAQ,IAAI,IAAI,QAAM,KAAK,QAAQ,SAAS,IAAI,KAAK,OAAO,SAAS,CAAC;AAAA,MAC1E;AACA,iBAAW,QAAQ,OAAQ,MAAK,UAAU,IAAI;AAC9C,WAAK,KAAK,UAAU,MAAM;AAC1B,WAAK,WAAW,QAAQ,QAAQ,GAAG;AAAA,IACvC;AAAA;AAAA,EAEc,UAAU,SAA+D;AAAA;AACnF,YAAM,WAAW,MAAM,QAAQ;AAAA,QAC3B,QAAQ,IAAI,IAAI,QAAM,KAAK,QAAQ,WAAW,IAAI,KAAK,OAAO,SAAS,CAAC;AAAA,MAC5E;AACA,iBAAW,QAAQ,SAAU,MAAK,UAAU,IAAI;AAChD,WAAK,KAAK,YAAY,QAAQ;AAC9B,WAAK,WAAW,UAAU,QAAQ,GAAG;AAAA,IACzC;AAAA;AAAA,EAEc,WAAW,SAAgE;AAAA;AACrF,YAAM,KAAK,QAAQ,SAAS,QAAQ,UAAU,QAAQ,UAAU;AAChE,WAAK,KAAK,aAAa,EAAE,UAAU,QAAQ,UAAU,YAAY,QAAQ,WAAW,CAAC;AACrF,WAAK,WAAW,WAAW,CAAC,CAAC;AAAA,IACjC;AAAA;AAAA,EAEc,YAAY,SAAiE;AAAA;AACvF,UAAI,CAAC,KAAK,QAAQ,iBAAiB;AAC/B,cAAM,IAAI,MAAM,kCAAkC;AAAA,MACtD;AACA,YAAM,UAAU,MAAM,KAAK,QAAQ,SAAS,QAAQ,MAAM;AAC1D,YAAM,WAAW,MAAM,KAAK,QAAQ,aAAa,QAAQ,QAAQ,SAAS,QAAQ,KAAK;AACvF,WAAK,KAAK,YAAY,QAAQ;AAC9B,WAAK,WAAW,YAAY,CAAC,QAAQ,MAAM,CAAC;AAAA,IAChD;AAAA;AAAA;AAAA,EAIM,QAAQ,IAA4C;AAAA;AACtD,UAAI,KAAK,MAAM,MAAM,IAAI,EAAE,EAAG,QAAO,KAAK,MAAM,MAAM,IAAI,EAAE;AAC5D,YAAM,OAAO,MAAM,KAAK,QAAQ,YAAY,EAAE;AAC9C,UAAI,KAAM,MAAK,UAAU,IAAI;AAC7B,aAAO;AAAA,IACX;AAAA;AAAA,EAEM,YAAY,UAAyB,SAAmE;AAAA;AAhUlH;AAiUQ,UAAI,CAAC,KAAK,MAAM,WAAW,IAAI,8BAAY,IAAI,GAAG;AAC9C,cAAM,KAAK,aAAa,QAAQ;AAAA,MACpC;AACA,YAAM,OAAM,UAAK,MAAM,WAAW,IAAI,8BAAY,MAAM,MAA5C,YAAiD,CAAC;AAC9D,YAAM,QAAQ,IACT,IAAI,QAAM,KAAK,MAAM,MAAM,IAAI,EAAE,CAAC,EAClC,OAAO,CAAC,MAA2B,CAAC,CAAC,CAAC,EACtC,OAAO,QAAK,mCAAS,kBAAiB,OAAO,EAAE,cAAc,IAAI;AAEtE,aAAO,KAAK,eAAe,OAAO,QAAQ;AAAA,IAC9C;AAAA;AAAA,EAEM,QAAQ,IAA6B;AAAA;AACvC,YAAM,QAAkB,CAAC;AACzB,UAAI,UAAiC,MAAM,KAAK,QAAQ,EAAE;AAC1D,aAAO,SAAS;AACZ,cAAM,QAAQ,QAAQ,IAAI;AAC1B,kBAAU,QAAQ,WAAW,MAAM,KAAK,QAAQ,QAAQ,QAAQ,IAAI;AAAA,MACxE;AACA,aAAO,MAAM,MAAM,KAAK,GAAG;AAAA,IAC/B;AAAA;AAAA,EAEM,SAAS,IAAiC;AAAA;AAC5C,aAAO,KAAK,QAAQ,SAAS,EAAE;AAAA,IACnC;AAAA;AAAA,EAEM,OAAO,OAAe,SAAoH;AAAA;AAC5I,aAAO,KAAK,QAAQ,OAAO,OAAO,OAAO;AAAA,IAC7C;AAAA;AAAA,EAEM,aAAwC;AAAA;AAC1C,aAAO,KAAK,QAAQ,WAAW;AAAA,IACnC;AAAA;AAAA,EAEM,aAAa,QAA4C;AAAA;AAC3D,UAAI,CAAC,KAAK,QAAQ,gBAAiB,OAAM,IAAI,MAAM,kCAAkC;AACrF,aAAO,KAAK,QAAQ,aAAa,MAAM;AAAA,IAC3C;AAAA;AAAA;AAAA,EAIc,eAAe,OAAyB,UAAoD;AAAA;AACtG,YAAM,QAAQ,MAAM,KAAK,QAAQ,SAAS,QAAQ;AAClD,UAAI,CAAC,MAAO,QAAO,KAAK,SAAS,KAAK;AAEtC,YAAM,WAAW,IAAI,IAAI,MAAM,IAAI,OAAK,EAAE,EAAE,CAAC;AAC7C,YAAM,aAAa,MAAM,WAAW,OAAO,QAAM,SAAS,IAAI,EAAE,CAAC;AACjE,YAAM,aAAa,IAAI,IAAI,UAAU;AACrC,YAAM,YAAY,MAAM,OAAO,OAAK,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC;AAEzD,YAAM,eAAe,WAAW,IAAI,QAAM,MAAM,KAAK,OAAK,EAAE,OAAO,EAAE,CAAE;AACvE,aAAO,CAAC,GAAG,cAAc,GAAG,KAAK,SAAS,SAAS,CAAC;AAAA,IACxD;AAAA;AAAA,EAEQ,SAAS,OAA2C;AACxD,WAAO,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM;AAC7B,UAAI,EAAE,SAAS,EAAE,KAAM,QAAO,EAAE,SAAS,WAAW,KAAK;AAEzD,YAAM,UAAU,EAAE,KAAK,cAAc,EAAE,IAAI;AAC3C,UAAI,YAAY,EAAG,QAAO;AAE1B,aAAO,EAAE,YAAY,EAAE;AAAA,IAC3B,CAAC;AAAA,EACL;AAAA;AAAA,EAIc,aAAa,UAAwC;AAAA;AAC/D,YAAM,QAAQ,MAAM,KAAK,QAAQ,YAAY,UAAU,EAAE,gBAAgB,KAAK,CAAC;AAC/E,WAAK,aAAa,KAAK;AACvB,WAAK,MAAM,WAAW,IAAI,8BAAY,IAAI;AAAA,IAC9C;AAAA;AAAA,EAEQ,aAAa,OAA+B;AAChD,eAAW,QAAQ,MAAO,MAAK,UAAU,IAAI;AAAA,EACjD;AAAA,EAEQ,UAAU,MAA4B;AA9YlD;AA+YQ,UAAM,WAAW,KAAK,MAAM,MAAM,IAAI,KAAK,EAAE;AAE7C,QAAI,UAAU;AACV,YAAM,UAAS,cAAS,aAAT,YAAqB;AACpC,YAAM,UAAS,UAAK,aAAL,YAAoB;AACnC,UAAI,WAAW,QAAQ;AACnB,cAAM,eAAc,UAAK,MAAM,WAAW,IAAI,MAAM,MAAhC,YAAqC,CAAC;AAC1D,aAAK,MAAM,WAAW,IAAI,QAAQ,YAAY,OAAO,QAAM,OAAO,KAAK,EAAE,CAAC;AAAA,MAC9E;AAAA,IACJ;AAEA,SAAK,MAAM,MAAM,IAAI,KAAK,IAAI,IAAI;AAElC,UAAM,aAAY,UAAK,aAAL,YAAiB;AACnC,UAAM,YAAY,UAAK,MAAM,WAAW,IAAI,SAAS,MAAnC,YAAwC,CAAC;AAC3D,QAAI,CAAC,SAAS,SAAS,KAAK,EAAE,GAAG;AAC7B,WAAK,MAAM,WAAW,IAAI,WAAW,CAAC,GAAG,UAAU,KAAK,EAAE,CAAC;AAAA,IAC/D;AAAA,EACJ;AAAA,EAEQ,UAAU,IAAkB;AAnaxC;AAoaQ,UAAM,OAAO,KAAK,MAAM,MAAM,IAAI,EAAE;AACpC,QAAI,CAAC,KAAM;AACX,SAAK,MAAM,MAAM,OAAO,EAAE;AAC1B,UAAM,aAAY,UAAK,aAAL,YAAiB;AACnC,UAAM,YAAY,UAAK,MAAM,WAAW,IAAI,SAAS,MAAnC,YAAwC,CAAC;AAC3D,SAAK,MAAM,WAAW,IAAI,WAAW,SAAS,OAAO,OAAK,MAAM,EAAE,CAAC;AAAA,EACvE;AAAA,EAEQ,oBAAoB,QAAyB;AA5azD;AA6aQ,eAAW,MAAM,OAAO,SAAS;AAC7B,YAAM,OAAO,KAAK,MAAM,MAAM,IAAI,EAAE;AACpC,UAAI,MAAM;AACN,cAAM,aAAY,UAAK,aAAL,YAAiB;AACnC,cAAM,YAAY,UAAK,MAAM,WAAW,IAAI,SAAS,MAAnC,YAAwC,CAAC;AAC3D,aAAK,MAAM,WAAW,IAAI,WAAW,SAAS,OAAO,OAAK,MAAM,EAAE,CAAC;AAEnE,YAAI,KAAK,SAAS,UAAU;AACxB,eAAK,MAAM,WAAW,OAAO,EAAE;AAAA,QACnC;AAEA,aAAK,MAAM,WAAW,QAAO,UAAK,aAAL,YAAiB,IAAI;AAAA,MACtD;AAEA,WAAK,MAAM,MAAM,OAAO,EAAE;AAAA,IAC9B;AAAA,EACJ;AAAA;AAAA,EAIc,YAAY,IAAqC;AAAA;AAC3D,YAAM,OAAO,MAAM,KAAK,QAAQ,EAAE;AAClC,UAAI,CAAC,KAAM,OAAM,IAAI,MAAM,mBAAmB,EAAE,EAAE;AAClD,aAAO;AAAA,IACX;AAAA;AAAA,EAEc,kBACV,UACA,MACA,MACA,WACa;AAAA;AACb,YAAM,WAAW,MAAM,KAAK,YAAY,QAAQ;AAChD,YAAM,WAAW,SAAS;AAAA,QAAK,OAC3B,EAAE,SAAS,QACX,EAAE,SAAS,QACX,EAAE,OAAO,aACT,EAAE,cAAc;AAAA,MACpB;AACA,UAAI,CAAC,SAAU;AACf,UAAI,KAAK,OAAO,wBAAwB,SAAS;AAC7C,cAAM,IAAI,MAAM,KAAK,IAAI,WAAW,IAAI,kBAAkB;AAAA,MAC9D;AAAA,IACJ;AAAA;AAAA,EAEQ,iBAAiB,SAA+B;AACpD,YAAQ,QAAQ,IAAI;AAAA,MAChB,KAAK;AAAY,eAAO,CAAC;AAAA,MACzB,KAAK;AAAY,eAAO,CAAC,QAAQ,EAAE;AAAA,MACnC,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAY,eAAO,QAAQ;AAAA,MAChC,KAAK;AAAY,eAAO,QAAQ;AAAA,MAChC,KAAK;AAAY,eAAO,CAAC,QAAQ,EAAE;AAAA,MACnC,KAAK;AAAY,eAAO,CAAC;AAAA,MACzB,KAAK;AAAY,eAAO,CAAC,QAAQ,MAAM;AAAA,IAC3C;AAAA,EACJ;AAAA;AAAA,EAIA,GAAuC,OAAU,UAAkD;AAC/F,QAAI,CAAC,KAAK,UAAU,KAAK,GAAG;AACxB,WAAK,UAAU,KAAK,IAAI,oBAAI,IAAI;AAAA,IACpC;AACA,IAAC,KAAK,UAAU,KAAK,EAAsC,IAAI,QAAQ;AACvE,WAAO,MAAO,KAAK,UAAU,KAAK,EAAsC,OAAO,QAAQ;AAAA,EAC3F;AAAA,EAEO,KACH,OACA,SACI;AAvfZ;AAwfQ,KAAC,UAAK,UAAU,KAAK,MAApB,mBACK,QAAQ,OAAK,EAAE,OAAO;AAAA,EAChC;AAAA,EAEQ,WAAW,IAAkB,SAAyB;AAC1D,SAAK;AACL,SAAK,KAAK,UAAU,EAAE,IAAI,SAAS,WAAW,KAAK,IAAI,EAAE,CAAC;AAAA,EAC9D;AACJ;;;AChgBA,SAAS,WAAW,QAAQ,UAAU,eAAsC;AA8GpE;AAvFD,SAAS,YAAyF;AAAA,EACrG;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,gBAAgB;AAAA,EAChB,iBAAiB,EAAE,UAAU,UAAU;AAAA,EACvC;AAAA,EACA,WAAW;AACf,GAA4B;AA/B5B;AAgCI,QAAM,iBAAiB,QAA0C,MAAG;AAhCxE,QAAAC,KAAAC,KAAA;AAgC4E;AAAA,MACpE,WAAqB,OAAO;AAAA,MAC5B,sBAAqBD,MAAA,OAAO,wBAAP,OAAAA,MAA8B;AAAA,MACnD,sBAAqBC,MAAA,OAAO,wBAAP,OAAAA,MAA8B;AAAA,MACnD,kBAAqB,YAAO,oBAAP,aAA2B,MAAM;AAAA,MACtD,SAAS;AAAA,QACL,eAAc,kBAAO,YAAP,mBAAgB,iBAAhB,YAAgC;AAAA,QAC9C,WAAc,kBAAO,YAAP,mBAAgB,aAAhB,YAA4B,EAAE,SAAS,OAAO,YAAY,IAAO;AAAA,MACnF;AAAA,IACJ;AAAA,KAAI,CAAC,MAAM,CAAC;AAEZ,QAAM,iBAAiB,OAAsC,oBAAI,IAAI,CAAC;AACtE,QAAM,CAAC,aAAa,cAAc,IAAI,SAAsB,oBAAI,IAAI,CAAC;AACrE,QAAM,oBAAmB,sDAAqB,qBAAgB,CAAC,MAAjB,mBAAoB,OAAzC,YAA+C;AAExE,YAAU,MAAM;AACZ,UAAM,WAAW,eAAe;AAChC,UAAM,cAAc,IAAI,IAAI,gBAAgB,IAAI,OAAK,EAAE,EAAE,CAAC;AAE1D,eAAW,CAAC,IAAI,MAAM,KAAK,UAAU;AACjC,UAAI,CAAC,YAAY,IAAI,EAAE,GAAG;AACtB,eAAO,QAAQ;AACf,iBAAS,OAAO,EAAE;AAClB,uBAAe,UAAQ;AACnB,gBAAM,OAAO,IAAI,IAAI,IAAI;AACzB,eAAK,OAAO,EAAE;AACd,iBAAO;AAAA,QACX,CAAC;AAAA,MACL;AAAA,IACJ;AAEA,UAAM,UAA2B,CAAC;AAElC,eAAW,aAAa,iBAAiB;AACrC,UAAI,SAAS,IAAI,UAAU,EAAE,EAAG;AAEhC,YAAM,SAAS,IAAI,UAAiB,UAAU,SAAS,cAAc;AACrE,eAAS,IAAI,UAAU,IAAI,MAAM;AAEjC,YAAM,cAAc,OAAO,KAAK,aAAa,EAAE,KAAK,MAAM;AACtD,uBAAe,UAAQ,IAAI,IAAI,IAAI,EAAE,IAAI,UAAU,EAAE,CAAC;AAAA,MAC1D,CAAC;AAED,cAAQ,KAAK,WAAW;AAAA,IAC5B;AAEA,YAAQ,WAAW,OAAO;AAE1B,WAAO,MAAM;AACT,iBAAW,UAAU,SAAS,OAAO,EAAG,QAAO,QAAQ;AACvD,eAAS,MAAM;AAAA,IACnB;AAAA,EAEJ,GAAG,CAAC,iBAAiB,aAAa,CAAC;AAInC,QAAM,eAAe,QAAQ,MAAM;AAC/B,UAAM,aAAa,oBAAI,IAAsC;AAE7D,eAAW,SAAS,iBAAiB;AACjC,YAAM,SAAS,eAAe,QAAQ,IAAI,MAAM,EAAE;AAClD,UAAI,CAAC,OAAQ;AACb,iBAAW,IAAI,MAAM,IAAI,EAAE,IAAI,MAAM,IAAI,MAAM,MAAM,MAAM,OAAO,CAAC;AAAA,IACvE;AAEA,WAAO;AAAA,MACH;AAAA,MACA,mBAAmB;AAAA,MACnB,QAAQ;AAAA,MACR;AAAA,MACA,SAAS,CAAC,OAAe,YAAY,IAAI,EAAE;AAAA,IAC/C;AAAA,EACJ,GAAG,CAAC,aAAa,iBAAiB,kBAAkB,gBAAgB,cAAc,CAAC;AAEnF,QAAM,WAAW,gBAAgB,MAAM,OAAK,YAAY,IAAI,EAAE,EAAE,CAAC;AAEjE,SACI,oBAAC,WAAW,UAAX,EAAoB,OAAO,cACvB,qBAAW,WAAW,UAC3B;AAER;","names":["_a","_b","_a","_b"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
interface VfsTab {
|
|
2
|
+
id: string;
|
|
3
|
+
nodeId: string;
|
|
4
|
+
workspaceId: string;
|
|
5
|
+
title: string;
|
|
6
|
+
isDirty: boolean;
|
|
7
|
+
isLocked: boolean;
|
|
8
|
+
savedContent: Uint8Array | null;
|
|
9
|
+
currentContent: Uint8Array | null;
|
|
10
|
+
lastSavedAt: number | null;
|
|
11
|
+
}
|
|
12
|
+
type DirtyChecker = (params: {
|
|
13
|
+
nodeId: string;
|
|
14
|
+
savedContent: Uint8Array | null;
|
|
15
|
+
currentContent: Uint8Array | null;
|
|
16
|
+
lastSavedAt: number | null;
|
|
17
|
+
}) => boolean;
|
|
18
|
+
interface UseVfsTabsOptions {
|
|
19
|
+
workspaceIds?: string[];
|
|
20
|
+
dirtyChecker?: DirtyChecker;
|
|
21
|
+
}
|
|
22
|
+
interface VfsTabsApi {
|
|
23
|
+
tabs: VfsTab[];
|
|
24
|
+
activeTabId: string | null;
|
|
25
|
+
activeTab: VfsTab | null;
|
|
26
|
+
open: (nodeId: string, workspaceId: string) => Promise<void>;
|
|
27
|
+
close: (tabId: string) => void;
|
|
28
|
+
closeOthers: (tabId: string) => void;
|
|
29
|
+
closeAll: (workspaceId?: string) => void;
|
|
30
|
+
setActive: (tabId: string) => void;
|
|
31
|
+
reorder: (activeId: string, overId: string) => void;
|
|
32
|
+
lock: (tabId: string) => void;
|
|
33
|
+
unlock: (tabId: string) => void;
|
|
34
|
+
markDirty: (tabId: string, currentContent: Uint8Array) => void;
|
|
35
|
+
markSaved: (tabId: string) => void;
|
|
36
|
+
}
|
|
37
|
+
declare function useVfsTabs(options?: UseVfsTabsOptions): VfsTabsApi;
|
|
38
|
+
|
|
39
|
+
export { type DirtyChecker as D, type UseVfsTabsOptions as U, type VfsTabsApi as V, type VfsTab as a, useVfsTabs as u };
|