buritifs 0.0.1

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.
@@ -0,0 +1,803 @@
1
+ // src/core/Explorer/file.ts
2
+ var ExplorerFile = class _ExplorerFile {
3
+ constructor(base, storage) {
4
+ this.ok = true;
5
+ this.error = null;
6
+ this.type = "file";
7
+ this.base = base;
8
+ this.storage = storage;
9
+ }
10
+ get path() {
11
+ return this.base;
12
+ }
13
+ // ─── Info ─────────────────────────────────────────────────
14
+ async info() {
15
+ return await this.storage.info({ path: this.base });
16
+ }
17
+ // ─── Refactor ─────────────────────────────────────────────
18
+ async rename({ name }) {
19
+ const result = await this.storage.rename({ path: this.base, name });
20
+ if (!result.ok) return result;
21
+ const parentPath = this.base.substring(0, this.base.lastIndexOf("/")) || "/";
22
+ this.base = `${parentPath === "/" ? "" : parentPath}/${name}`;
23
+ return { ok: true, error: null };
24
+ }
25
+ async copy({ to, priority }) {
26
+ const result = await this.storage.copy({ fromPath: this.base, toPath: to, merge: false, priority });
27
+ if (!result.ok) return result;
28
+ return await this.storage.source({ path: to });
29
+ }
30
+ async move({ to, force }) {
31
+ const result = await this.storage.move({ fromPath: this.base, toPath: to, force });
32
+ if (!result.ok) return result;
33
+ const newInstance = await this.storage.source({ path: to });
34
+ if (newInstance instanceof _ExplorerFile) this.base = newInstance.base;
35
+ return { ok: true, error: null };
36
+ }
37
+ async delete() {
38
+ return await this.storage.delete({ path: this.base });
39
+ }
40
+ async exists() {
41
+ return await this.storage.exists({ path: this.base });
42
+ }
43
+ async write({ content }) {
44
+ return await this.storage.write({ path: this.base, content });
45
+ }
46
+ async read() {
47
+ return await this.storage.read({ path: this.base });
48
+ }
49
+ };
50
+
51
+ // src/core/Explorer/folder.ts
52
+ var ExplorerFolder = class _ExplorerFolder {
53
+ constructor(base, storage) {
54
+ this.ok = true;
55
+ this.error = null;
56
+ this.type = "folder";
57
+ this.base = base == "/" ? base : base.endsWith("/") ? base : `${base}/`;
58
+ this.storage = storage;
59
+ }
60
+ get path() {
61
+ return this.base === "/" ? "/" : this.base.slice(0, -1);
62
+ }
63
+ get tree() {
64
+ return this.storage;
65
+ }
66
+ // ─── Info ─────────────────────────────────────────────────
67
+ async info() {
68
+ return await this.storage.info({ path: this.path });
69
+ }
70
+ // ─── Get ──────────────────────────────────────────────────
71
+ async get({ name }) {
72
+ const path = `${this.base}${name}`;
73
+ return await this.storage.source({ path });
74
+ }
75
+ // ─── New ──────────────────────────────────────────────────
76
+ async newFolder({ name }) {
77
+ const path = `${this.base}${name}`;
78
+ return await this.storage.newFolder({ path });
79
+ }
80
+ async newFile({ name }) {
81
+ const path = `${this.base}${name}`;
82
+ return await this.storage.newFile({ path });
83
+ }
84
+ // ─── Refactor ─────────────────────────────────────────────
85
+ async rename({ name }) {
86
+ const path = this.base === "/" ? "/" : this.base.slice(0, -1);
87
+ const result = await this.storage.rename({ path, name });
88
+ if (!result.ok) return result;
89
+ const parentPath = path.substring(0, path.lastIndexOf("/")) || "/";
90
+ this.base = `${parentPath === "/" ? "" : parentPath}/${name}/`;
91
+ return { ok: true, error: null };
92
+ }
93
+ async copy({ to, merge, priority }) {
94
+ const result = await this.storage.copy({ fromPath: this.base, toPath: to, merge, priority });
95
+ if (!result.ok) return result;
96
+ return await this.storage.source({ path: to });
97
+ }
98
+ async move({ to, force }) {
99
+ const result = await this.storage.move({ fromPath: this.base, toPath: to, force });
100
+ if (!result.ok) return result;
101
+ const newInstance = await this.storage.source({ path: to });
102
+ if (newInstance instanceof _ExplorerFolder) this.base = newInstance.base;
103
+ return { ok: true, error: null };
104
+ }
105
+ async delete() {
106
+ return await this.storage.delete({ path: this.base });
107
+ }
108
+ async list({ recursive, limit, page, filter } = {}) {
109
+ return await this.storage.list({ path: this.path, recursive, limit, page, filter });
110
+ }
111
+ async exists() {
112
+ return await this.storage.exists({ path: this.path });
113
+ }
114
+ async size({ recursive, filter } = {}) {
115
+ return await this.storage.size({ path: this.path, recursive, filter });
116
+ }
117
+ };
118
+
119
+ // src/core/utils.ts
120
+ function validatePath(oldPath) {
121
+ let path = oldPath;
122
+ if (!path.startsWith("/")) path = `/${path}`;
123
+ if (path.endsWith("/") && path !== "/") path = path.slice(0, -1);
124
+ if (path.trim() === "") return { newPath: oldPath, error: "Path cannot be empty" };
125
+ if (path.includes("//")) return { newPath: oldPath, error: 'Path cannot contain "//"' };
126
+ if (path.endsWith("/") && path !== "/") return { newPath: oldPath, error: 'Path cannot end with "/"' };
127
+ if (path.includes(" ")) return { newPath: oldPath, error: "Path cannot contain spaces" };
128
+ return { newPath: path, error: null };
129
+ }
130
+
131
+ // src/core/storage/idb-setup.ts
132
+ var _IDBSetup = class _IDBSetup {
133
+ constructor({ name }) {
134
+ this.db = null;
135
+ this.dbName = name;
136
+ }
137
+ // ─── Helpers ───────────────────────────────────────────────
138
+ request(mode, fn) {
139
+ return new Promise((resolve, reject) => {
140
+ const transaction = this.db.transaction("nodes", mode);
141
+ const store = transaction.objectStore("nodes");
142
+ const req = fn(store);
143
+ req.onsuccess = () => resolve(req.result);
144
+ req.onerror = (e) => reject(e.target.error);
145
+ });
146
+ }
147
+ transact(mode, fn) {
148
+ return new Promise((resolve, reject) => {
149
+ const transaction = this.db.transaction("nodes", mode);
150
+ const store = transaction.objectStore("nodes");
151
+ fn(store);
152
+ transaction.oncomplete = () => resolve();
153
+ transaction.onerror = (e) => reject(e.target.error);
154
+ });
155
+ }
156
+ async pathTrated(path) {
157
+ if (path === "/") {
158
+ return {
159
+ path,
160
+ parent: null,
161
+ name: path,
162
+ table: await this.request("readonly", (store) => store.get(path))
163
+ };
164
+ }
165
+ ;
166
+ const { newPath, error } = validatePath(path);
167
+ if (error) throw new Error(error);
168
+ const pathParts = newPath.split("/");
169
+ const parent = pathParts.length === 2 ? "/" : pathParts.slice(0, -1).join("/");
170
+ const parentNode = await this.request("readonly", (store) => store.get(parent));
171
+ if (!parentNode) throw new Error(`Parent path "${parent}" does not exist`);
172
+ if (parentNode.type !== "folder") throw new Error(`Parent path "${parent}" is not a folder`);
173
+ const existingNode = await this.request("readonly", (store) => store.get(newPath));
174
+ return { path: newPath, parent, name: pathParts.pop(), table: existingNode };
175
+ }
176
+ async withWAL(data, action) {
177
+ await this.transact("readwrite", (store) => store.put({ ...data, status: "pending" }));
178
+ await action();
179
+ await this.transact("readwrite", (store) => store.put({ ...data, status: "ready" }));
180
+ }
181
+ // ─── init ──────────────────────────────────────────────────
182
+ openDB() {
183
+ const existing = _IDBSetup.registry.get(this.dbName);
184
+ if (existing) {
185
+ this.db = existing;
186
+ return Promise.resolve();
187
+ }
188
+ return new Promise((resolve, reject) => {
189
+ const request = indexedDB.open(this.dbName, _IDBSetup.DB_VERSION);
190
+ request.onsuccess = (event) => {
191
+ this.db = event.target.result;
192
+ _IDBSetup.registry.set(this.dbName, this.db);
193
+ resolve();
194
+ };
195
+ request.onerror = (event) => {
196
+ reject(event.target.error);
197
+ };
198
+ request.onblocked = () => {
199
+ reject(new Error("IndexedDB blocked: close other tabs that are using the database."));
200
+ };
201
+ request.onupgradeneeded = (event) => {
202
+ const db = event.target.result;
203
+ if (event.oldVersion < 1) {
204
+ const store = db.createObjectStore("nodes", { keyPath: "path" });
205
+ store.createIndex("type", "type");
206
+ store.createIndex("parent", "parent");
207
+ store.createIndex("contentId", "contentId", { unique: true });
208
+ store.createIndex("status", "status");
209
+ }
210
+ };
211
+ });
212
+ }
213
+ static close({ name }) {
214
+ const db = _IDBSetup.registry.get(name);
215
+ if (db) {
216
+ db.close();
217
+ _IDBSetup.registry.delete(name);
218
+ }
219
+ }
220
+ };
221
+ _IDBSetup.registry = /* @__PURE__ */ new Map();
222
+ _IDBSetup.DB_VERSION = 1;
223
+ var IDBSetup = _IDBSetup;
224
+
225
+ // src/core/storage/opfs-storage.ts
226
+ var OPFSStorage = class extends IDBSetup {
227
+ constructor(props) {
228
+ super(props);
229
+ }
230
+ async openStorage() {
231
+ this.root = await navigator.storage.getDirectory();
232
+ if (!this.root) throw new Error("Mensagem de erro");
233
+ }
234
+ async writeStorage(id, content) {
235
+ const fileHandle = await this.root.getFileHandle(id, { create: true });
236
+ const writable = await fileHandle.createWritable();
237
+ const data = typeof content === "object" && !(content instanceof ArrayBuffer) ? JSON.stringify(content) : content;
238
+ await writable.write(data);
239
+ await writable.close();
240
+ }
241
+ async readStorage(id) {
242
+ const fileHandle = await this.root.getFileHandle(id);
243
+ const file = await fileHandle.getFile();
244
+ return await file.arrayBuffer();
245
+ }
246
+ async readTextStorage(id) {
247
+ const fileHandle = await this.root.getFileHandle(id);
248
+ const file = await fileHandle.getFile();
249
+ return await file.text();
250
+ }
251
+ async deleteStorage(id) {
252
+ await this.root.removeEntry(id);
253
+ }
254
+ async existsStorage(id) {
255
+ try {
256
+ await this.root.getFileHandle(id);
257
+ return true;
258
+ } catch {
259
+ return false;
260
+ }
261
+ }
262
+ };
263
+
264
+ // src/core/storage/idb-nodes.ts
265
+ var IDBNodes = class extends OPFSStorage {
266
+ constructor(props) {
267
+ super(props);
268
+ }
269
+ async getSource(props) {
270
+ const propsTrated = await this.pathTrated(props.path);
271
+ if (!propsTrated.table) throw new Error(`Path ${props.path} does not exist`);
272
+ return propsTrated.table;
273
+ }
274
+ async addNode({ path, type }) {
275
+ if (type !== "file" && type !== "folder") throw new Error("Type must be 'file' or 'folder'");
276
+ if (path == "/") throw new Error("Should not create the root folder.");
277
+ const propsTrated = await this.pathTrated(path);
278
+ if (propsTrated.path === "/") throw new Error("Cannot add root node");
279
+ if (propsTrated.table && propsTrated.table.type !== type) throw new Error(`Path "${path}" already exists as a "${propsTrated.table.type}"`);
280
+ const now = Date.now();
281
+ const contentId = type === "file" ? String(crypto.getRandomValues(new Uint32Array(1))[0]) : void 0;
282
+ const node = {
283
+ path: propsTrated.path,
284
+ parent: propsTrated.parent,
285
+ type,
286
+ createdAt: propsTrated.table?.createdAt ?? now,
287
+ updatedAt: now,
288
+ ...contentId ? { contentId } : {}
289
+ };
290
+ await this.withWAL(node, async () => {
291
+ if (type === "file") await this.writeStorage(contentId, "");
292
+ });
293
+ }
294
+ async removeNode({ path }) {
295
+ if (path === "/") throw new Error("Cannot remove root node.");
296
+ const node = await this.getSource({ path });
297
+ if (node.type === "file") {
298
+ await this.transact("readwrite", (store) => store.delete(node.path));
299
+ await this.deleteStorage(node.contentId).catch(() => {
300
+ });
301
+ return;
302
+ }
303
+ if (node.type === "folder") {
304
+ const range = IDBKeyRange.bound(node.path + "/", node.path + "/\uFFFF");
305
+ const children = await this.request("readonly", (store) => store.getAll(range));
306
+ await this.transact("readwrite", (store) => {
307
+ store.delete(range);
308
+ store.delete(node.path);
309
+ });
310
+ for (const child of children) {
311
+ if (child.type === "file") {
312
+ await this.deleteStorage(child.contentId).catch(() => {
313
+ });
314
+ }
315
+ }
316
+ return;
317
+ }
318
+ throw new Error(`Unknown node type "${node.type}" at path "${path}".`);
319
+ }
320
+ };
321
+
322
+ // src/core/storage/idb-query.ts
323
+ var IDBQuery = class extends IDBNodes {
324
+ constructor(props) {
325
+ super(props);
326
+ }
327
+ async existsNode({ path }) {
328
+ const norm = await this.pathTrated(path);
329
+ return !!norm.table;
330
+ }
331
+ async sizeNode({
332
+ pathRef,
333
+ recursive = false,
334
+ filter
335
+ }) {
336
+ const norm = await this.pathTrated(pathRef);
337
+ if (!norm.table) throw new Error(`Path "${pathRef}" does not exist`);
338
+ if (norm.table.type !== "folder") throw new Error(`Path "${pathRef}" is not a folder`);
339
+ const recursiveRange = norm.path === "/" ? IDBKeyRange.lowerBound("/", true) : IDBKeyRange.bound(norm.path + "/", norm.path + "/\uFFFF");
340
+ let count = 0;
341
+ await this.transact("readonly", (store) => {
342
+ const req = recursive ? store.openCursor(recursiveRange) : store.index("parent").openCursor(IDBKeyRange.only(norm.path));
343
+ req.onsuccess = (e) => {
344
+ const cursor = e.target.result;
345
+ if (!cursor) return;
346
+ const node = cursor.value;
347
+ if (node.status !== "pending") {
348
+ const item = { path: node.path, type: node.type, createdAt: node.createdAt, updatedAt: node.updatedAt };
349
+ if (!filter || filter(item)) count++;
350
+ }
351
+ cursor.continue();
352
+ };
353
+ });
354
+ return count;
355
+ }
356
+ async listNodes({
357
+ pathRef,
358
+ recursive = false,
359
+ limit = -1,
360
+ page = 0,
361
+ filter
362
+ }) {
363
+ const norm = await this.pathTrated(pathRef);
364
+ if (!norm.table) throw new Error(`Path "${pathRef}" does not exist`);
365
+ if (norm.table.type !== "folder") throw new Error(`Path "${pathRef}" is not a folder`);
366
+ const recursiveRange = norm.path === "/" ? IDBKeyRange.lowerBound("/", true) : IDBKeyRange.bound(norm.path + "/", norm.path + "/\uFFFF");
367
+ const results = [];
368
+ let toSkip = limit === -1 ? 0 : page * limit;
369
+ let count = 0;
370
+ await this.transact("readonly", ((store) => {
371
+ const req = recursive ? store.openCursor(recursiveRange) : store.index("parent").openCursor(IDBKeyRange.only(norm.path));
372
+ req.onsuccess = (e) => {
373
+ const cursor = e.target.result;
374
+ if (!cursor) return;
375
+ const node = cursor.value;
376
+ if (node.status === "pending") {
377
+ cursor.continue();
378
+ return;
379
+ }
380
+ const item = {
381
+ path: node.path,
382
+ type: node.type,
383
+ createdAt: node.createdAt,
384
+ updatedAt: node.updatedAt
385
+ };
386
+ if (filter && !filter(item)) {
387
+ cursor.continue();
388
+ return;
389
+ }
390
+ if (toSkip > 0) {
391
+ toSkip--;
392
+ cursor.continue();
393
+ return;
394
+ }
395
+ if (limit !== -1 && count >= limit) return;
396
+ results.push(item);
397
+ count++;
398
+ cursor.continue();
399
+ };
400
+ }));
401
+ return results;
402
+ }
403
+ };
404
+
405
+ // src/core/storage/idb-refactor.ts
406
+ var IDBRefactor = class extends IDBQuery {
407
+ constructor(props) {
408
+ super(props);
409
+ }
410
+ async copyNode({
411
+ fromPath,
412
+ toPath,
413
+ merge = false,
414
+ priority = "source"
415
+ }) {
416
+ if (fromPath === "/" || toPath === "/") throw new Error("Cannot copy root node.");
417
+ if (fromPath === toPath) throw new Error("Cannot copy a node to itself.");
418
+ const fromEntity = await this.getSource({ path: fromPath });
419
+ const toEntityProps = await this.pathTrated(toPath);
420
+ const now = Date.now();
421
+ const remap = (oldPath) => toEntityProps.path + oldPath.slice(fromEntity.path.length);
422
+ const remapParent = (oldParent) => oldParent === null ? null : oldParent === fromEntity.path ? toEntityProps.path : remap(oldParent);
423
+ if (fromEntity.type === "file") {
424
+ if (priority === "destination" && !!toEntityProps.table) return;
425
+ const newContentId = String(crypto.getRandomValues(new Uint32Array(1))[0]);
426
+ const content = await this.readStorage(fromEntity.contentId);
427
+ await this.writeStorage(newContentId, content);
428
+ await this.transact("readwrite", (store) => {
429
+ if (toEntityProps.table) store.delete(toEntityProps.path);
430
+ store.put({
431
+ path: toEntityProps.path,
432
+ parent: toEntityProps.parent,
433
+ type: "file",
434
+ contentId: newContentId,
435
+ createdAt: fromEntity.createdAt,
436
+ updatedAt: now,
437
+ status: "ready"
438
+ });
439
+ });
440
+ if (toEntityProps.table?.type === "file") {
441
+ await this.deleteStorage(toEntityProps.table.contentId).catch(() => {
442
+ });
443
+ }
444
+ return;
445
+ }
446
+ if (fromEntity.type === "folder") {
447
+ const destExists = !!toEntityProps.table;
448
+ const destIsFile = toEntityProps.table?.type === "file";
449
+ if (priority === "destination" && !merge && destExists) return;
450
+ if (priority === "destination" && destIsFile) return;
451
+ const skipExisting = merge && priority === "destination";
452
+ const shouldReplaceDest = destExists && (!merge || destIsFile);
453
+ const sourceRange = IDBKeyRange.bound(fromEntity.path + "/", fromEntity.path + "/\uFFFF");
454
+ const sourceChildren = await this.request("readonly", (store) => store.getAll(sourceRange));
455
+ let destNodesToClean = [];
456
+ if (shouldReplaceDest) {
457
+ if (destIsFile && toEntityProps.table) {
458
+ destNodesToClean = [toEntityProps.table];
459
+ } else {
460
+ const destRange = IDBKeyRange.bound(toEntityProps.path + "/", toEntityProps.path + "/\uFFFF");
461
+ destNodesToClean = await this.request("readonly", (store) => store.getAll(destRange));
462
+ }
463
+ }
464
+ const foldersToCreate = [
465
+ { path: toEntityProps.path, parent: toEntityProps.parent, createdAt: toEntityProps.table?.createdAt ?? now }
466
+ ];
467
+ const filesToCopy = [];
468
+ for (const node of sourceChildren) {
469
+ if (node.status === "pending") continue;
470
+ const newPath = remap(node.path);
471
+ const newParent = remapParent(node.parent);
472
+ if (node.type === "folder") {
473
+ if (skipExisting && await this.existsNode({ path: newPath })) continue;
474
+ foldersToCreate.push({ path: newPath, parent: newParent, createdAt: node.createdAt });
475
+ } else if (node.type === "file") {
476
+ if (skipExisting && await this.existsNode({ path: newPath })) continue;
477
+ filesToCopy.push({
478
+ fromContentId: node.contentId,
479
+ newContentId: String(crypto.getRandomValues(new Uint32Array(1))[0]),
480
+ newPath,
481
+ newParent,
482
+ createdAt: node.createdAt
483
+ });
484
+ }
485
+ }
486
+ for (const file of filesToCopy) {
487
+ const content = await this.readStorage(file.fromContentId);
488
+ await this.writeStorage(file.newContentId, content);
489
+ }
490
+ await this.transact("readwrite", (store) => {
491
+ if (shouldReplaceDest) {
492
+ store.delete(toEntityProps.path);
493
+ if (!destIsFile) {
494
+ const destRange = IDBKeyRange.bound(toEntityProps.path + "/", toEntityProps.path + "/\uFFFF");
495
+ store.delete(destRange);
496
+ }
497
+ }
498
+ for (const folder of foldersToCreate) {
499
+ store.put({ path: folder.path, parent: folder.parent, type: "folder", createdAt: folder.createdAt, updatedAt: now, status: "ready" });
500
+ }
501
+ for (const file of filesToCopy) {
502
+ store.put({ path: file.newPath, parent: file.newParent, type: "file", contentId: file.newContentId, createdAt: file.createdAt, updatedAt: now, status: "ready" });
503
+ }
504
+ });
505
+ for (const node of destNodesToClean) {
506
+ if (node.type === "file") {
507
+ await this.deleteStorage(node.contentId).catch(() => {
508
+ });
509
+ }
510
+ }
511
+ }
512
+ }
513
+ async moveNode({
514
+ fromPath,
515
+ toPath,
516
+ force = false
517
+ }) {
518
+ if (fromPath === "/" || toPath === "/") throw new Error("Cannot move root node.");
519
+ const fromNorm = await this.pathTrated(fromPath);
520
+ const toNorm = await this.pathTrated(toPath);
521
+ if (fromNorm.path === toNorm.path) throw new Error("Cannot move a node to itself.");
522
+ if (toNorm.path.startsWith(fromNorm.path + "/")) throw new Error("Cannot move a folder into one of its own descendants.");
523
+ const fromEntity = await this.getSource({ path: fromPath });
524
+ const destExists = !!toNorm.table;
525
+ if (destExists) {
526
+ if (!force) throw new Error(`Destination "${toNorm.path}" already exists.`);
527
+ await this.removeNode({ path: toNorm.path });
528
+ }
529
+ const now = Date.now();
530
+ const remap = (oldPath) => toNorm.path + oldPath.slice(fromNorm.path.length);
531
+ const remapParent = (oldParent) => oldParent === null ? null : oldParent === fromNorm.path ? toNorm.path : remap(oldParent);
532
+ if (fromEntity.type === "file") {
533
+ await this.transact("readwrite", (store) => {
534
+ store.delete(fromNorm.path);
535
+ store.put({ ...fromEntity, path: toNorm.path, parent: toNorm.parent, updatedAt: now });
536
+ });
537
+ return;
538
+ }
539
+ if (fromEntity.type === "folder") {
540
+ const range = IDBKeyRange.bound(fromNorm.path + "/", fromNorm.path + "/\uFFFF");
541
+ const children = await this.request("readonly", (store) => store.getAll(range));
542
+ await this.transact("readwrite", (store) => {
543
+ store.delete(fromNorm.path);
544
+ store.put({ ...fromEntity, path: toNorm.path, parent: toNorm.parent, updatedAt: now });
545
+ for (const node of children) {
546
+ if (node.status === "pending") continue;
547
+ const newPath = remap(node.path);
548
+ const newParent = remapParent(node.parent);
549
+ store.delete(node.path);
550
+ store.put({ ...node, path: newPath, parent: newParent, updatedAt: now });
551
+ }
552
+ });
553
+ }
554
+ }
555
+ };
556
+
557
+ // src/core/storage/storage-init.ts
558
+ var StorageInit = class extends IDBRefactor {
559
+ constructor(props) {
560
+ super(props);
561
+ }
562
+ async initExplorerData() {
563
+ await this.openDB();
564
+ await this.openStorage();
565
+ await this.recover();
566
+ }
567
+ async recover() {
568
+ await this.recoverPending();
569
+ await this.recoverOrphans();
570
+ await this.recoverBroken();
571
+ }
572
+ async recoverPending() {
573
+ const pending = await this.request(
574
+ "readonly",
575
+ (store) => store.index("status").getAll("pending")
576
+ );
577
+ for (const node of pending) {
578
+ await this.removeNode({ path: node.path });
579
+ if (node.type === "file") {
580
+ await this.deleteStorage(node.contentId).catch(() => {
581
+ });
582
+ }
583
+ }
584
+ }
585
+ async recoverOrphans() {
586
+ if (!(Symbol.asyncIterator in this.root)) return;
587
+ for await (const [name, handle] of this.root) {
588
+ if (handle.kind !== "file") continue;
589
+ const linked = await this.request(
590
+ "readonly",
591
+ (store) => store.index("contentId").get(name)
592
+ );
593
+ if (!linked) {
594
+ await this.deleteStorage(name).catch(() => {
595
+ });
596
+ }
597
+ }
598
+ }
599
+ async recoverBroken() {
600
+ const files = await this.request(
601
+ "readonly",
602
+ (store) => store.index("type").getAll("file")
603
+ );
604
+ for (const node of files) {
605
+ if (node.type !== "file") continue;
606
+ const exists = await this.existsStorage(node.contentId);
607
+ if (!exists) {
608
+ await this.removeNode({ path: node.path }).catch(() => {
609
+ });
610
+ }
611
+ }
612
+ }
613
+ };
614
+
615
+ // src/core/Explorer/ExplorerMain.ts
616
+ var ExplorerTree = class _ExplorerTree extends StorageInit {
617
+ constructor(props) {
618
+ super(props);
619
+ this.listeners = /* @__PURE__ */ new Map();
620
+ }
621
+ subscribe(path, fn) {
622
+ if (!this.listeners.has(path)) this.listeners.set(path, /* @__PURE__ */ new Set());
623
+ this.listeners.get(path).add(fn);
624
+ return () => this.listeners.get(path)?.delete(fn);
625
+ }
626
+ parentOf(path) {
627
+ const idx = path.lastIndexOf("/");
628
+ if (idx <= 0) return "/";
629
+ return path.slice(0, idx);
630
+ }
631
+ notify(path) {
632
+ let current = path;
633
+ while (current !== null) {
634
+ this.listeners.get(current)?.forEach((fn) => fn());
635
+ if (current === "/") break;
636
+ current = this.parentOf(current);
637
+ }
638
+ }
639
+ static async create(props) {
640
+ const instance = new _ExplorerTree(props);
641
+ try {
642
+ await instance.initExplorerData();
643
+ const now = Date.now();
644
+ const existing = await instance.request("readonly", (store) => store.get("/"));
645
+ const objectPathRootInitialize = {
646
+ path: "/",
647
+ type: "folder",
648
+ parent: null,
649
+ createdAt: existing?.createdAt ?? now,
650
+ updatedAt: now,
651
+ status: "ready"
652
+ };
653
+ await instance.transact("readwrite", (store) => store.put(objectPathRootInitialize));
654
+ } catch (e) {
655
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
656
+ }
657
+ return instance;
658
+ }
659
+ // ─── Get ──────────────────────────────────────────────────
660
+ async source({ path }) {
661
+ try {
662
+ const table = await this.getSource({ path });
663
+ if (table.type === "folder") return new ExplorerFolder(table.path, this);
664
+ if (table.type === "file") return new ExplorerFile(table.path, this);
665
+ return { ok: false, error: `Internal error: entity at "${path}" has no valid type` };
666
+ } catch (e) {
667
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
668
+ }
669
+ }
670
+ // ─── Info ─────────────────────────────────────────────────
671
+ async info({ path }) {
672
+ try {
673
+ const table = await this.getSource({ path });
674
+ return { ok: true, error: null, path: table.path, createdAt: table.createdAt, updatedAt: table.updatedAt };
675
+ } catch (e) {
676
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
677
+ }
678
+ }
679
+ async type({ path }) {
680
+ try {
681
+ const entity = await this.getSource({ path });
682
+ return entity.path === "/" ? "folder" : entity.type;
683
+ } catch (e) {
684
+ return null;
685
+ }
686
+ }
687
+ // ─── New ──────────────────────────────────────────────────
688
+ async newFolder({ path }) {
689
+ try {
690
+ await this.addNode({ path, type: "folder" });
691
+ this.notify(this.parentOf(path));
692
+ return new ExplorerFolder(path, this);
693
+ } catch (e) {
694
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
695
+ }
696
+ }
697
+ async newFile({ path }) {
698
+ try {
699
+ await this.addNode({ path, type: "file" });
700
+ this.notify(this.parentOf(path));
701
+ return new ExplorerFile(path, this);
702
+ } catch (e) {
703
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
704
+ }
705
+ }
706
+ // ─── Refactor ─────────────────────────────────────────────
707
+ async delete({ path }) {
708
+ try {
709
+ const parent = this.parentOf(path);
710
+ await this.removeNode({ path });
711
+ this.notify(parent);
712
+ return { ok: true, error: null };
713
+ } catch (e) {
714
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
715
+ }
716
+ }
717
+ async copy({ fromPath, toPath, merge, priority }) {
718
+ try {
719
+ await this.copyNode({ fromPath, toPath, merge, priority });
720
+ this.notify(this.parentOf(toPath));
721
+ return { ok: true, error: null };
722
+ } catch (e) {
723
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
724
+ }
725
+ }
726
+ async move({ fromPath, toPath, force }) {
727
+ try {
728
+ await this.moveNode({ fromPath, toPath, force });
729
+ this.notify(this.parentOf(fromPath));
730
+ this.notify(this.parentOf(toPath));
731
+ return { ok: true, error: null };
732
+ } catch (e) {
733
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
734
+ }
735
+ }
736
+ async rename({ path, name }) {
737
+ try {
738
+ const normalized = await this.pathTrated(path);
739
+ const parent = normalized.parent ?? "/";
740
+ const toPath = `${parent === "/" ? "" : parent}/${name}`;
741
+ await this.moveNode({ fromPath: normalized.path, toPath });
742
+ this.notify(parent);
743
+ return { ok: true, error: null };
744
+ } catch (e) {
745
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
746
+ }
747
+ }
748
+ async list({ path, recursive, limit, page, filter }) {
749
+ try {
750
+ const items = await this.listNodes({ pathRef: path, recursive, limit, page, filter });
751
+ return { ok: true, error: null, items };
752
+ } catch (e) {
753
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
754
+ }
755
+ }
756
+ async exists({ path }) {
757
+ try {
758
+ return await this.existsNode({ path });
759
+ } catch {
760
+ return false;
761
+ }
762
+ }
763
+ async size({ path, recursive, filter }) {
764
+ try {
765
+ const size = await this.sizeNode({ pathRef: path, recursive, filter });
766
+ return { ok: true, error: null, size };
767
+ } catch (e) {
768
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
769
+ }
770
+ }
771
+ async write({ path, content }) {
772
+ try {
773
+ const tableByDB = await this.getSource({ path });
774
+ if (tableByDB.type === "folder") return { ok: false, error: `Path "${path}" is a folder` };
775
+ await this.writeStorage(tableByDB.contentId, content);
776
+ await this.transact("readwrite", (store) => store.put({ ...tableByDB, updatedAt: Date.now() }));
777
+ this.notify(path);
778
+ return { ok: true, error: null };
779
+ } catch (e) {
780
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
781
+ }
782
+ }
783
+ async read({ path }) {
784
+ try {
785
+ const tableByDB = await this.getSource({ path });
786
+ if (tableByDB.type === "folder") return { ok: false, error: `Path "${path}" is a folder` };
787
+ const content = await this.readStorage(tableByDB.contentId);
788
+ const text = new TextDecoder().decode(content);
789
+ return { ok: true, error: null, content, text };
790
+ } catch (e) {
791
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
792
+ }
793
+ }
794
+ // zip. Talvez futuramente.
795
+ // Sync Github. Talvez futuramente.
796
+ };
797
+
798
+ export {
799
+ ExplorerFile,
800
+ ExplorerFolder,
801
+ ExplorerTree
802
+ };
803
+ //# sourceMappingURL=chunk-QOM3Q3WE.js.map