1function _array_like_to_array(arr, len) { 2 if (len == null || len > arr.length) len = arr.length; 3 for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i]; 4 return arr2; 5} 6function _array_without_holes(arr) { 7 if (Array.isArray(arr)) return _array_like_to_array(arr); 8} 9function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { 10 try { 11 var info = gen[key](arg); 12 var value = info.value; 13 } catch (error) { 14 reject(error); 15 return; 16 } 17 if (info.done) { 18 resolve(value); 19 } else { 20 Promise.resolve(value).then(_next, _throw); 21 } 22} 23function _async_to_generator(fn) { 24 return function() { 25 var self = this, args = arguments; 26 return new Promise(function(resolve, reject) { 27 var gen = fn.apply(self, args); 28 function _next(value) { 29 asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); 30 } 31 function _throw(err) { 32 asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); 33 } 34 _next(undefined); 35 }); 36 }; 37} 38function _class_call_check(instance, Constructor) { 39 if (!(instance instanceof Constructor)) { 40 throw new TypeError("Cannot call a class as a function"); 41 } 42} 43function _defineProperties(target, props) { 44 for(var i = 0; i < props.length; i++){ 45 var descriptor = props[i]; 46 descriptor.enumerable = descriptor.enumerable || false; 47 descriptor.configurable = true; 48 if ("value" in descriptor) descriptor.writable = true; 49 Object.defineProperty(target, descriptor.key, descriptor); 50 } 51} 52function _create_class(Constructor, protoProps, staticProps) { 53 if (protoProps) _defineProperties(Constructor.prototype, protoProps); 54 if (staticProps) _defineProperties(Constructor, staticProps); 55 return Constructor; 56} 57function _define_property(obj, key, value) { 58 if (key in obj) { 59 Object.defineProperty(obj, key, { 60 value: value, 61 enumerable: true, 62 configurable: true, 63 writable: true 64 }); 65 } else { 66 obj[key] = value; 67 } 68 return obj; 69} 70function _instanceof(left, right) { 71 if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { 72 return !!right[Symbol.hasInstance](left); 73 } else { 74 return left instanceof right; 75 } 76} 77function _iterable_to_array(iter) { 78 if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); 79} 80function _non_iterable_spread() { 81 throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); 82} 83function _to_consumable_array(arr) { 84 return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread(); 85} 86function _unsupported_iterable_to_array(o, minLen) { 87 if (!o) return; 88 if (typeof o === "string") return _array_like_to_array(o, minLen); 89 var n = Object.prototype.toString.call(o).slice(8, -1); 90 if (n === "Object" && o.constructor) n = o.constructor.name; 91 if (n === "Map" || n === "Set") return Array.from(n); 92 if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen); 93} 94function _ts_generator(thisArg, body) { 95 var f, y, t, g, _ = { 96 label: 0, 97 sent: function() { 98 if (t[0] & 1) throw t[1]; 99 return t[1]; 100 }, 101 trys: [], 102 ops: [] 103 }; 104 return(g = { 105 next: verb(0), 106 "throw": verb(1), 107 "return": verb(2) 108 }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { 109 return this; 110 }), g); 111 function verb(n) { 112 return function(v) { 113 return step([ 114 n, 115 v 116 ]); 117 }; 118 } 119 function step(op) { 120 if (f) throw new TypeError("Generator is already executing."); 121 while(_)try {
122 if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 123 if (y = 0, t) op = [ 124 op[0] & 2, 125 t.value 126 ]; 127 switch(op[0]){ 128 case 0: 129 case 1: 130 t = op; 131 break; 132 case 4: 133 _.label++; 134 return { 135 value: op[1], 136 done: false 137 }; 138 case 5: 139 _.label++; 140 y = op[1]; 141 op = [ 142 0 143 ]; 144 continue; 145 case 7: 146 op = _.ops.pop(); 147 _.trys.pop(); 148 continue; 149 default: 150 if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { 151 _ = 0; 152 continue; 153 } 154 if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) { 155 _.label = op[1]; 156 break; 157 } 158 if (op[0] === 6 && _.label < t[1]) { 159 _.label = t[1]; 160 t = op; 161 break; 162 } 163 if (t && _.label < t[2]) { 164 _.label = t[2]; 165 _.ops.push(op); 166 break; 167 } 168 if (t[2]) _.ops.pop(); 169 _.trys.pop(); 170 continue; 171 } 172 op = body.call(thisArg, _); 173 } catch (e) { 174 op = [ 175 6, 176 e 177 ]; 178 y = 0; 179 } finally{ 180 f = t = 0; 181 } 182 if (op[0] & 5) throw op[1]; 183 return { 184 value: op[0] ? op[1] : void 0, 185 done: true 186 }; 187 } 188} 189import { FileSystemNode } from "../models/FileSystemNode.js"; 190/** 191 * Service for interacting with the FileSystem API 192 * 193 * Handles API calls, caching, and event dispatching for file system operations. 194 * Uses opaque node identifiers from the API - never parses or constructs them client-side. 195 * 196 * THE WIRE, AND WHY IT LOOKS LIKE THIS 197 * ------------------------------------ 198 * Every call below goes to `/api/v1/files*` â the same official, 199 * documented, scope-gated endpoints a third-party API client or an MCP 200 * agent drives. There is no longer a private `/fs/*` surface behind the 201 * file manager, which is the whole point of the exercise: one 202 * implementation, one authorization path, one set of bugs. 203 * 204 * Two things the endpoints do differently for us, both deliberate and 205 * both documented server-side: 206 * 207 * - `Accept: application/jsonl` asks for the flat node shape rather 208 * than a JSON:API document. That shape embeds a directory's ancestor 209 * chain and a symlink's resolved target inline, which the tree, the 210 * breadcrumb and the open-a-symlink path all need on every row, and 211 * which JSON:API would put in `included` â somewhere a *stream* has 212 * nowhere to put. The listing additionally comes back unpaginated, 213 * because this client sorts the complete set client-side. 214 * - Writes send a JSON:API request envelope (`{data: {type, â¦}}`), 215 * because the request half of the codec is not negotiable. 216 * 217 * Failures are always JSON:API `errors` documents, hence 218 * `readErrorMessage()`. The result shapes this class *returns* are 219 * unchanged from when it spoke to `/fs/*`: it is the seam, and the 220 * controllers above it should not know the wire changed. 221 */ export var FileSystemService = /*#__PURE__*/ function() { 222 "use strict"; 223 function FileSystemService(config) { 224 _class_call_check(this, FileSystemService); 225 _define_property(this, "config", void 0); 226 _define_property(this, "cache", new Map()); 227 _define_property(this, "eventHandlers", new Map()); 228 _define_property(this, "abortControllers", new Map()); 229 this.config = config; 230 } 231 _create_class(FileSystemService, [ 232 { 233 key: "getCachedNode", 234 value: /** 235 * Get node from cache (uses state controller if provided, otherwise internal
235cache) 236 */ function getCachedNode(identifier) { 237 if (this.config.stateController) { 238 return this.config.stateController.getCachedNode(identifier); 239 } 240 return this.cache.get(identifier); 241 } 242 }, 243 { 244 key: "hasInCache", 245 value: /** 246 * Check if node is in cache (uses state controller if provided, otherwise internal cache) 247 */ function hasInCache(identifier) { 248 if (this.config.stateController) { 249 return this.config.stateController.hasNodeInCache(identifier); 250 } 251 return this.cache.has(identifier); 252 } 253 }, 254 { 255 key: "setCachedNode", 256 value: /** 257 * Add node to cache (uses state controller if provided, otherwise internal cache) 258 */ function setCachedNode(identifier, node) { 259 if (this.config.stateController) { 260 this.config.stateController.cacheNode(identifier, node); 261 } else { 262 this.cache.set(identifier, node); 263 } 264 } 265 }, 266 { 267 key: "handleAuthenticationError", 268 value: /** 269 * Check if response indicates authentication failure and reload page if so. 270 * Returns true if the page will reload (caller should stop processing). 271 */ function handleAuthenticationError(response) { 272 // Check for 401 Unauthorized (session expired) 273 if (response.status === 401) { 274 window.location.reload(); 275 return true; 276 } 277 // A file served from the content endpoint carries an attachment 278 // disposition and may legitimately be text/html â the user is allowed to 279 // edit their own HTML. The login page never sets that header, so this 280 // check has to come before the one below or opening any .html file 281 // reloads the page. 282 var disposition = response.headers.get("Content-Disposition") || ""; 283 if (disposition.includes("attachment")) { 284 return false; 285 } 286 // Check if a successful response returned HTML instead of JSON (login page redirect). 287 // Only check on 2xx/3xx responses; 4xx/5xx HTML responses are legitimate server errors, 288 // not login redirects (e.g. Symfony error pages for permission denied on mode 0 directories). 289 if (response.ok) { 290 var contentType = response.headers.get("Content-Type") || ""; 291 if (contentType.includes("text/html")) { 292 window.location.reload(); 293 return true; 294 } 295 } 296 return false; 297 } 298 }, 299 { 300 /** 301 * Subscribe to file system events 302 */ key: "on", 303 value: function on(type, handler) { 304 var _this = this; 305 if (!this.eventHandlers.has(type)) { 306 this.eventHandlers.set(type, new Set()); 307 } 308 this.eventHandlers.get(type).add(handler); 309 // Return unsubscribe function 310 return function() { 311 var _this_eventHandlers_get; 312 (_this_eventHandlers_get = _this.eventHandlers.get(type)) === null || _this_eventHandlers_get === void 0 ? void 0 : _this_eventHandlers_get.delete(handler); 313 }; 314 } 315 }, 316 { 317 key: "emit", 318 value: /** 319 * Emit an event to all subscribers 320 */ function emit(event) { 321 var handlers = this.eventHandlers.get(event.type); 322 if (handlers) { 323 handlers.forEach(function(handler) { 324 return handler(event); 325 }); 326 } 327 } 328 }, 329 { 330 key: "nodeUrl", 331 value: /** 332 * The URL of one node. 333 * 334 * The identifier is base64url, so it needs no escaping to be a path 335 * segment â it is encoded anyway, because "the identifier is opaque" 336 * is a promise this client keeps rather than an assumption it makes. 337 */ function nodeUrl(identifier) { 338 var suffix = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : ""; 339 return "/api/v1/files/".concat(encodeURIComponent(identifier)).concat(suffix); 340 } 341 }, 342 { 343 key: "postAction", 344 value: /** 345 * Perform one of the verbs on a node. 346 * 347 * A verb does not get a route of its own: `POST {id}/actions` with 348 * the action named by the posted `data.type` is how the whole API 349 * spells "do this to that". The response is the flat shape â an 350 * affected node for most actions, a report for the removals and the 351 * batches. 352 */ function postAction(identifier, action) { 353 var attributes = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {}; 354 var _this = this; 355 return _async_to_generator(function() { 356 return _ts_generator(this, function(_state) { 357 switch(_state.label){ 358 case 0: 359 return [ 360 4, 361 _this.send("POST", _this.nodeUrl(identifier, "/actions"), { 362 type: action, 363 attributes: attributes 364 }) 365 ]; 366 case 1: 367 return [ 368 2, 369 _state.sent() 370 ]; 371 } 372 }); 373 })(); 374 } 375 }, 376 { 377 key: "send", 378 value: /** 379 * Send a JSON:API resource object and read the flat answer. 380 * 381 * The asymmetry is the codec's: request bodies must be JSON:API 382 * envelopes, responses are negotiated, and this client asks for the 383 * flat one. 384 */ function send(method, url, resource) { 385 var _this = this; 386 return _async_to_generator(function() { 387 var response; 388 return _ts_generator(this, function(_state) { 389 switch(_state.label){ 390 case 0: 391 return [ 392 4, 393 fetch(url, { 394 method: method, 395 headers: { 396 "Accept": FileSystemService.FLAT, 397 "Content-Type": FileSystemService.JSON_API 398 }, 399 body: JSON.stringify({ 400 data: resource 401 }) 402 }) 403 ]; 404 case 1: 405 response = _state.sent(); 406 return [ 407 4, 408 _this.readJson(response) 409 ]; 410 case 2: 411 return [ 412 2, 413 _state.sent() 414 ]; 415 } 416 }); 417 })(); 418 } 419 }, 420 { 421 key: "request", 422 value: /** 423 * A GET or DELETE answering the flat shape. 424 */ function request(method, url) { 425 var _this = this; 426 return _async_to_generator(function() { 427 var response; 428 return _ts_generator(this, function(_state) { 429 switch(_state.label){ 430 case 0: 431 return [ 432 4, 433 fetch(url, { 434 method: method, 435 headers: { 436 "Accept": FileSystemService.FLAT 437 } 438 }) 439 ]; 440 case 1: 441 response = _state.sent(); 442 return [ 443 4, 444 _this.readJson(response) 445 ]; 446 case 2: 447 return [ 448 2, 449 _state.sent() 450 ]; 451 } 452 }); 453 })(); 454 } 455 }, 456 { 457 key: "readJson", 458 value: /** 459 * Session expiry first, then the error document, then the body. 460 */ function readJson(response) { 461 var _this = this; 462 return _async_to_generator(function() { 463 var _; 464 return _ts_generator(this, function(_state) { 465 switch(_state.label){ 466 case 0: 467 if (_this.handleAuthenticationError(response)) { 468 throw new Error("Session expired"); 469 } 470 if (!!response.ok) return [ 471 3, 472 2 473 ]; 474 _ = Error.bind; 475 return [ 476 4, 477 _this.readErrorMessage(response) 478 ]; 479 case 1: 480 throw new (_.apply(Error, [ 481 void 0, 482 _state.sent() 483 ])); 484 case 2: 485 return [ 486 4, 487 response.json() 488 ]; 489 case 3: 490 return [ 491 2, 492 _state.sent() 493 ]; 494 } 495 }); 496 })(); 497 } 498 }, 499 { 500 key: "cancelPendingRequest", 501 value: /**
502 * Cancel any pending request 503 */ function cancelPendingRequest(key) { 504 var controller = this.abortControllers.get(key); 505 if (controller) { 506 controller.abort(); 507 this.abortControllers.delete(key); 508 } 509 } 510 }, 511 { 512 key: "getNode", 513 value: /** 514 * Fetch metadata for a single node by its opaque identifier 515 */ function getNode(identifier) { 516 var useCache = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : true; 517 var _this = this; 518 return _async_to_generator(function() { 519 var controller, response, _, data, node, error; 520 return _ts_generator(this, function(_state) { 521 switch(_state.label){ 522 case 0: 523 // Check cache first 524 if (useCache && _this.hasInCache(identifier)) { 525 return [ 526 2, 527 _this.getCachedNode(identifier) 528 ]; 529 } 530 _this.cancelPendingRequest("meta:".concat(identifier)); 531 controller = new AbortController(); 532 _this.abortControllers.set("meta:".concat(identifier), controller); 533 _this.emit({ 534 type: "node:loading", 535 identifier: identifier 536 }); 537 _state.label = 1; 538 case 1: 539 _state.trys.push([ 540 1, 541 6, 542 7, 543 8 544 ]); 545 return [ 546 4, 547 fetch(_this.nodeUrl(identifier), { 548 signal: controller.signal, 549 headers: { 550 "Accept": FileSystemService.FLAT 551 } 552 }) 553 ]; 554 case 2: 555 response = _state.sent(); 556 // Check for authentication failure before processing response 557 if (_this.handleAuthenticationError(response)) { 558 throw new Error("Session expired"); 559 } 560 if (!!response.ok) return [ 561 3, 562 4 563 ]; 564 _ = Error.bind; 565 return [ 566 4, 567 _this.readErrorMessage(response) 568 ]; 569 case 3: 570 throw new (_.apply(Error, [ 571 void 0, 572 _state.sent() 573 ])); 574 case 4: 575 return [ 576 4, 577 response.json() 578 ]; 579 case 5: 580 data = _state.sent(); 581 node = FileSystemNode.fromData(data); 582 // Cache the node by its identifier 583 _this.setCachedNode(identifier, node); 584 _this.emit({ 585 type: "node:loaded", 586 identifier: identifier, 587 node: node 588 }); 589 return [ 590 2, 591 node 592 ]; 593 case 6: 594 error = _state.sent(); 595 if (_instanceof(error, Error) && error.
595name === "AbortError") { 596 throw error; // Re-throw abort errors 597 } 598 _this.emit({ 599 type: "node:error", 600 identifier: identifier, 601 error: error 602 }); 603 throw error; 604 case 7: 605 _this.abortControllers.delete("meta:".concat(identifier)); 606 return [ 607 7 608 ]; 609 case 8: 610 return [ 611 2 612 ]; 613 } 614 }); 615 })(); 616 } 617 }, 618 { 619 key: "getChildren", 620 value: /** 621 * Fetch children of a directory using streaming JSONL 622 */ function getChildren(identifier) { 623 var _this = this; 624 return _async_to_generator(function() { 625 var children; 626 return _ts_generator(this, function(_state) { 627 switch(_state.label){ 628 case 0: 629 children = []; 630 return [ 631 4, 632 _this.getChildrenStreaming(identifier, function(child) { 633 children.push(child); 634 }) 635 ]; 636 case 1: 637 _state.sent(); 638 return [ 639 2, 640 children 641 ]; 642 } 643 }); 644 })(); 645 } 646 }, 647 { 648 key: "getChildrenStreaming", 649 value: /** 650 * Fetch children of a directory using streaming JSONL with incremental callback. 651 * Calls onChild for each child as it's parsed from the stream. 652 * Also detects and emits events when subdirectories are found. 653 */ function getChildrenStreaming(identifier, onChild) { 654 var _this = this; 655 return _async_to_generator(function() { 656 var controller, foundSubdirectory, parentNode, onChildWithSubdirectoryDetection, response, _, allChildren, error; 657 return _ts_generator(this, function(_state) { 658 switch(_state.label){ 659 case 0: 660 _this.cancelPendingRequest("list:".concat(identifier)); 661 controller = new AbortController(); 662 _this.abortControllers.set("list:".concat(identifier), controller); 663 _this.emit({ 664 type: "children:loading", 665 identifier: identifier 666 }); 667 // Track if we've found a subdirectory 668 foundSubdirectory = false; 669 parentNode = _this.getCachedNode(identifier); 670 // Wrapper to detect subdirectories during streaming 671 onChildWithSubdirectoryDetection = function(child) { 672 // If this is the first directory found, emit event and update parent 673 if (!foundSubdirectory && child.isDirectory()) { 674 foundSubdirectory = true; 675 if (parentNode && parentNode.isDirectory()) { 676 parentNode.hasSubdirectory = true; 677 _this.emit({ 678 type: "directory:hasSubdirectory", 679 identifier: identifier, 680 node: parentNode 681 }); 682 } 683 } 684 onChild(child); 685 }; 686 _state.label = 1; 687 case 1: 688 _state.trys.push([ 689 1, 690 6, 691 7, 692 8 693 ]); 694 return [ 695 4, 696 fetch("/api/v1/files?filter%5Bdirectory%5D=".concat(encodeURIComponent(identifier)), { 697 signal: controller.signal, 698 headers: { 699 "Accept": FileSystemService.FLAT 700 } 701 }) 702 ]; 703 case 2: 704 response = _state.sent(); 705 // Check for authentication failure before processing response 706 if (_this.handleAuthenticationError(response)) { 707 throw new Error("Session expired"); 708 } 709 if (!!response.ok) return [ 710 3, 711 4 712 ]; 713 _ = Error.bind; 714 return [ 715 4, 716 _this.readErrorMessage(response) 717 ]; 718 case 3: 719 throw new (_.apply(Error, [ 720 void 0, 721 _state.sent() 722 ])); 723 case 4: 724 return [ 725 4, 726 _this.parseJsonlResponseStreaming(response, onChildWithSubdirectoryDetection) 727 ]; 728 case 5: 729 allChildren = _state.sent(); 730 // Update cache for the parent directory if it exists 731 if (parentNode && parentNode.isDirectory()) { 732 parentNode.children = allChildren; 733 // After all children loaded, if no subdirectory was found, set hasSubdirectory to false 734 if (!foundSubdirectory) { 735 parentNode.hasSubdirectory = false;
736 } 737 } 738 // Cache each child by its identifier 739 allChildren.forEach(function(child) { 740 _this.setCachedNode(child.id, child); 741 }); 742 _this.emit({ 743 type: "children:loaded", 744 identifier: identifier, 745 children: allChildren 746 }); 747 return [ 748 3, 749 8 750 ]; 751 case 6: 752 error = _state.sent(); 753 if (_instanceof(error, Error) && error.name === "AbortError") { 754 throw error; 755 } 756 _this.emit({ 757 type: "children:error", 758 identifier: identifier, 759 error: error 760 }); 761 throw error; 762 case 7: 763 _this.abortControllers.delete("list:".concat(identifier)); 764 return [ 765 7 766 ]; 767 case 8: 768 return [ 769 2 770 ]; 771 } 772 }); 773 })(); 774 } 775 }, 776 { 777 key: "parseJsonlResponseStreaming", 778 value: /** 779 * Parse JSONL (JSON Lines) response with streaming callback 780 */ function parseJsonlResponseStreaming(response, onChild) { 781 return _async_to_generator(function() { 782 var children, text, lines, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, line, data, node, reader, decoder, buffer, _ref, done, value, data1, node1, lines1, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, line1, trimmedLine, data2, node2; 783 return _ts_generator(this, function(_state) { 784 switch(_state.label){ 785 case 0: 786 children = []; 787 if (!!response.body) return [ 788 3, 789 2 790 ]; 791 return [ 792 4, 793 response.text() 794 ]; 795 case 1: 796 text = _state.sent(); 797 lines = text.trim().split("\n").filter(function(line) { 798 return line.length > 0; 799 }); 800 _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined; 801 try { 802 for(_iterator = lines[Symbol.iterator](); !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){ 803 line = _step.value; 804 data = JSON.parse(line); 805 node = FileSystemNode.fromData(data); 806 children.push(node); 807 onChild(node); 808 } 809 } catch (err) { 810 _didIteratorError = true; 811 _iteratorError = err; 812 } finally{ 813 try { 814 if (!_iteratorNormalCompletion && _iterator.return != null) { 815 _iterator.return(); 816 } 817 } finally{ 818 if (_didIteratorError) { 819 throw _iteratorError; 820 } 821 } 822 } 823 return [ 824 2, 825 children 826 ]; 827 case 2: 828 reader = response.body.getReader(); 829 decoder = new TextDecoder(); 830 buffer = ""; 831 _state.label = 3; 832 case 3: 833 _state.trys.push([ 834 3, 835 , 836 7, 837 8 838 ]); 839 _state.label = 4; 840 case 4: 841 if (!true) return [ 842 3, 843 6 844 ]; 845 return [ 846 4, 847 reader.read() 848 ]; 849 case 5: 850 _ref = _state.sent(), done = _ref.done, value = _ref.value; 851 if (done) { 852 // Process any remaining data in buffer 853 if (buffer.trim()) { 854 data1 = JSON.parse(buffer.trim()); 855 node1 = FileSystemNode.fromData(data1); 856 children.push(node1); 857 onChild(node1); 858 } 859 return [ 860 3, 861 6 862 ]; 863 } 864 buffer += decoder.decode(value, { 865 stream: true 866 }); 867 // Process complete lines 868 lines1 = buffer.split("\n"); 869 // Keep the last incomplete line in the buffer 870 buffer = lines1.pop() || ""; 871 _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined; 872 try { 873 for(_iterator1 = lines1[Symbol.iterator](); !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){ 874 line1 = _step1.value; 875 trimmedLine = line1.trim(); 876 if (trimmedLine) { 877 data2 = JSON.parse(trimmedLine); 878 node2 = FileSystemNode.fromData(data2); 879 children.push(node2); 880 onChild(node2); 881 } 882 } 883 } catch (err) {
884 _didIteratorError1 = true; 885 _iteratorError1 = err; 886 } finally{ 887 try { 888 if (!_iteratorNormalCompletion1 && _iterator1.return != null) { 889 _iterator1.return(); 890 } 891 } finally{ 892 if (_didIteratorError1) { 893 throw _iteratorError1; 894 } 895 } 896 } 897 return [ 898 3, 899 4 900 ]; 901 case 6: 902 return [ 903 3, 904 8 905 ]; 906 case 7: 907 reader.releaseLock(); 908 return [ 909 7 910 ]; 911 case 8: 912 return [ 913 2, 914 children 915 ]; 916 } 917 }); 918 })(); 919 } 920 }, 921 { 922 key: "loadDirectory", 923 value: /** 924 * Load a directory and its children by identifier 925 */ function loadDirectory(identifier) { 926 var _this = this; 927 return _async_to_generator(function() { 928 var node, children; 929 return _ts_generator(this, function(_state) { 930 switch(_state.label){ 931 case 0: 932 return [ 933 4, 934 _this.getNode(identifier) 935 ]; 936 case 1: 937 node = _state.sent(); 938 if (!node.isDirectory()) { 939 throw new Error("Node is not a directory"); 940 } 941 if (!!node.hasLoadedChildren) return [ 942 3, 943 5 944 ]; 945 node.isLoading = true; 946 _state.label = 2; 947 case 2: 948 _state.trys.push([ 949 2, 950 , 951 4, 952 5 953 ]); 954 return [ 955 4, 956 _this.getChildren(identifier) 957 ]; 958 case 3: 959 children = _state.sent(); 960 node.children = children; 961 return [ 962 3, 963 5 964 ]; 965 case 4: 966 node.isLoading = false; 967 return [ 968 7 969 ]; 970 case 5: 971 return [ 972 2, 973 node 974 ]; 975 } 976 }); 977 })(); 978 } 979 }, 980 { 981 key: "loadDirectoryStreaming", 982 value: /** 983 * Load a directory with streaming children updates. 984 * Calls onChildrenUpdate with the node and accumulated children as they stream in (debounced). 985 */ function loadDirectoryStreaming(identifier, onChildrenUpdate) { 986 var debounceMs = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : 50; 987 var _this = this; 988 return _async_to_generator(function() { 989 var node, debounceTimer, pendingUpdate, scheduleUpdate; 990 return _ts_generator(this, function(_state) { 991 switch(_state.label){ 992 case 0: 993 return [ 994 4, 995 _this.getNode(identifier) 996 ]; 997 case 1: 998 node = _state.sent(); 999 if (!node.isDirectory()) { 1000 throw new Error("Node is not a directory"); 1001 } 1002 if (!!node.hasLoadedChildren) return [ 1003 3, 1004 6 1005 ]; 1006 node.isLoading = true; 1007 node.children = []; // Initialize empty array 1008 debounceTimer = null; 1009 pendingUpdate = false;
1010 scheduleUpdate = function() { 1011 pendingUpdate = true; 1012 if (!debounceTimer) { 1013 debounceTimer = setTimeout(function() { 1014 debounceTimer = null; 1015 if (pendingUpdate) { 1016 pendingUpdate = false; 1017 onChildrenUpdate(node, _to_consumable_array(node.children)); 1018 } 1019 }, debounceMs); 1020 } 1021 }; 1022 _state.label = 2; 1023 case 2: 1024 _state.trys.push([ 1025 2, 1026 , 1027 4, 1028 5 1029 ]); 1030 return [ 1031 4, 1032 _this.getChildrenStreaming(identifier, function(child) { 1033 node.children.push(child); 1034 scheduleUpdate(); 1035 }) 1036 ]; 1037 case 3: 1038 _state.sent(); 1039 // Final flush - ensure last update is sent 1040 if (debounceTimer) { 1041 clearTimeout(debounceTimer); 1042 } 1043 onChildrenUpdate(node, _to_consumable_array(node.children)); 1044 return [ 1045 3, 1046 5 1047 ]; 1048 case 4: 1049 node.isLoading = false; 1050 if (debounceTimer) { 1051 clearTimeout(debounceTimer); 1052 } 1053 return [ 1054 7 1055 ]; 1056 case 5: 1057 return [ 1058 3, 1059 7 1060 ]; 1061 case 6: 1062 // Already loaded, just call the callback with existing children 1063 onChildrenUpdate(node, _to_consumable_array(node.children)); 1064 _state.label = 7; 1065 case 7: 1066 return [ 1067 2, 1068 node 1069 ]; 1070 } 1071 }); 1072 })(); 1073 } 1074 }, 1075 { 1076 /** 1077 * Get the download URL for a file by its opaque identifier. 1078 * 1079 * This is the same endpoint the editor reads from; the attachment 1080 * disposition it responds with is what makes a browser save the file 1081 * instead of navigating to it. 1082 */ key: "getDownloadUrl", 1083 value: function getDownloadUrl(identifier) { 1084 return this.nodeUrl(identifier, "/content"); 1085 } 1086 }, 1087 { 1088 /** 1089 * Get the upload URL for a directory by its opaque identifier 1090 */ key: "getUploadUrl", 1091 value: function getUploadUrl(identifier) { 1092 return this.nodeUrl(identifier, "/uploads"); 1093 } 1094 }, 1095 { 1096 key: "trash", 1097 value: /** 1098 * Move a file or directory to trash. 1099 * Returns a TrashResult with status indicating success, not_found, or error. 1100 */ function trash(identifier) { 1101 var _this = this; 1102 return _async_to_generator(function() { 1103 var report; 1104 return _ts_generator(this, function(_state) { 1105 switch(_state.label){ 1106 case 0: 1107 return [ 1108 4, 1109 _this.postAction(identifier, "file-trash") 1110 ]; 1111 case 1: 1112 report = _state.sent(); 1113 // Invalidate cache for the trashed identifier (regardless of status) 1114 _this.invalidate(identifier); 1115 return [ 1116 2, 1117 { 1118 success: true, 1119 status: report.status === "already-absent" ? "not_found" : "trashed" 1120 } 1121 ]; 1122 } 1123 }); 1124 })(); 1125 } 1126 }, 1127 { 1128 key: "trashMultiple", 1129 value: /** 1130 * Move multiple files or directories to trash. 1131 * Returns an object with successful identifiers (including not_found) and errors. 1132 * Identifiers that were 'not_found' are still considered success since the end state is the same. 1133 */ function trashMultiple(identifiers) { 1134 var _this = this; 1135 return _async_to_generator(function() { 1136 var results, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, identifier, result, error, err; 1137 return _ts_generator(this, function(_state) { 1138 switch(_state.label){ 1139 case 0: 1140 results = { 1141 success: [], 1142 notFound: [], 1143 errors: [] 1144 }; 1145 _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined; 1146 _state.label = 1; 1147 case 1: 1148 _state.trys.push([ 1149 1, 1150 8, 1151 9, 1152 10 1153 ]); 1154 _iterator = identifiers[Symbol.iterator](); 1155 _state.label = 2; 1156 case 2: 1157 if (!!(_iteratorNormalCompletion = (_step = _iterator.next()).done)) return [ 1158 3, 1159 7 1160 ]; 1161 identifier = _step.value; 1162 _state.label = 3; 1163 case 3: 1164 _state.trys.push([ 1165 3, 1166 5, 1167 , 1168 6 1169 ]); 1170 return [ 1171 4, 1172 _this.trash(identifier) 1173 ]; 1174 case 4: 1175 result = _state.sent();
1176 if (result.status === "not_found") { 1177 results.notFound.push(identifier); 1178 } else { 1179 results.success.push(identifier); 1180 } 1181 return [ 1182 3, 1183 6 1184 ]; 1185 case 5: 1186 error = _state.sent(); 1187 results.errors.push({ 1188 identifier: identifier, 1189 error: _instanceof(error, Error) ? error.message : String(error) 1190 }); 1191 return [ 1192 3, 1193 6 1194 ]; 1195 case 6: 1196 _iteratorNormalCompletion = true; 1197 return [ 1198 3, 1199 2 1200 ]; 1201 case 7: 1202 return [ 1203 3, 1204 10 1205 ]; 1206 case 8: 1207 err = _state.sent(); 1208 _didIteratorError = true; 1209 _iteratorError = err; 1210 return [ 1211 3, 1212 10 1213 ]; 1214 case 9: 1215 try { 1216 if (!_iteratorNormalCompletion && _iterator.return != null) { 1217 _iterator.return(); 1218 } 1219 } finally{ 1220 if (_didIteratorError) { 1221 throw _iteratorError; 1222 } 1223 } 1224 return [ 1225 7 1226 ]; 1227 case 10: 1228 return [ 1229 2, 1230 results 1231 ]; 1232 } 1233 }); 1234 })(); 1235 } 1236 }, 1237 { 1238 key: "permanentDelete", 1239 value: /** 1240 * Permanently delete a file or directory (bypassing trash). 1241 * Returns a DeleteResult with status indicating success, not_found, or error. 1242 */ function permanentDelete(identifier) { 1243 var _this = this; 1244 return _async_to_generator(function() { 1245 var report; 1246 return _ts_generator(this, function(_state) { 1247 switch(_state.label){ 1248 case 0: 1249 return [ 1250 4, 1251 _this.request("DELETE", _this.nodeUrl(identifier)) 1252 ]; 1253 case 1: 1254 report = _state.sent(); 1255 // Invalidate cache for the deleted identifier (regardless of status) 1256 _this.invalidate(identifier); 1257 return [ 1258 2, 1259 { 1260 success: true, 1261 status: report.status === "already-absent" ? "not_found" : "deleted" 1262 } 1263 ]; 1264 } 1265 }); 1266 })(); 1267 } 1268 }, 1269 { 1270 key: "permanentDeleteMultiple", 1271 value: /** 1272 * Permanently delete multiple files or directories (bypassing trash). 1273 * Returns an object with successful identifiers (including not_found) and errors. 1274 */ function permanentDeleteMultiple(identifiers) { 1275 var _this = this; 1276 return _async_to_generator(function() { 1277 var results, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, identifier, result, error, err; 1278 return _ts_generator(this, function(_state) { 1279 switch(_state.label){ 1280 case 0: 1281 results = { 1282 success: [], 1283 notFound: [], 1284 errors: [] 1285 }; 1286 _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined; 1287 _state.label = 1; 1288 case 1: 1289 _state.trys.push([ 1290 1, 1291 8, 1292 9, 1293 10 1294 ]); 1295 _iterator = identifiers[Symbol.iterator](); 1296 _state.label = 2; 1297 case 2: 1298 if (!!(_iteratorNormalCompletion = (_step = _iterator.next()).done)) return [ 1299 3, 1300 7 1301 ]; 1302 identifier = _step.value; 1303 _state.label = 3; 1304 case 3: 1305 _state.trys.push([ 1306 3, 1307 5, 1308 , 1309 6 1310 ]); 1311 return [ 1312 4, 1313 _this.permanentDelete(identifier) 1314 ]; 1315 case 4: 1316 result = _state.sent();
1317 if (result.status === "not_found") { 1318 results.notFound.push(identifier); 1319 } else { 1320 results.success.push(identifier); 1321 } 1322 return [ 1323 3, 1324 6 1325 ]; 1326 case 5: 1327 error = _state.sent(); 1328 results.errors.push({ 1329 identifier: identifier, 1330 error: _instanceof(error, Error) ? error.message : String(error) 1331 }); 1332 return [ 1333 3, 1334 6 1335 ]; 1336 case 6: 1337 _iteratorNormalCompletion = true; 1338 return [ 1339 3, 1340 2 1341 ]; 1342 case 7: 1343 return [ 1344 3, 1345 10 1346 ]; 1347 case 8: 1348 err = _state.sent(); 1349 _didIteratorError = true; 1350 _iteratorError = err; 1351 return [ 1352 3, 1353 10 1354 ]; 1355 case 9: 1356 try { 1357 if (!_iteratorNormalCompletion && _iterator.return != null) { 1358 _iterator.return(); 1359 } 1360 } finally{ 1361 if (_didIteratorError) { 1362 throw _iteratorError; 1363 } 1364 } 1365 return [ 1366 7 1367 ]; 1368 case 10: 1369 return [ 1370 2, 1371 results 1372 ]; 1373 } 1374 }); 1375 })(); 1376 } 1377 }, 1378 { 1379 key: "restore", 1380 value: /** 1381 * Restore a file or directory from trash. 1382 * Returns a RestoreResult with status indicating success or error. 1383 */ function restore(identifier) { 1384 var _this = this; 1385 return _async_to_generator(function() { 1386 var node; 1387 return _ts_generator(this, function(_state) { 1388 switch(_state.label){ 1389 case 0: 1390 return [ 1391 4, 1392 _this.postAction(identifier, "file-restore") 1393 ]; 1394 case 1: 1395 node = _state.sent(); 1396 // Invalidate cache for the restored identifier 1397 _this.invalidate(identifier); 1398 return [ 1399 2, 1400 { 1401 success: true, 1402 status: "restored", 1403 node: node 1404 } 1405 ]; 1406 } 1407 }); 1408 })(); 1409 } 1410 }, 1411 { 1412 key: "restoreMultiple", 1413 value: /** 1414 * Restore multiple entries from trash. 1415 */ function restoreMultiple(identifiers) { 1416 var _this = this; 1417 return _async_to_generator(function() { 1418 var results, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, identifier, error, err; 1419 return _ts_generator(this, function(_state) { 1420 switch(_state.label){ 1421 case 0: 1422 results = { 1423 success: [], 1424 errors: [] 1425 }; 1426 _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined; 1427 _state.label = 1; 1428 case 1: 1429 _state.trys.push([ 1430 1, 1431 8, 1432 9, 1433 10 1434 ]); 1435 _iterator = identifiers[Symbol.iterator](); 1436 _state.label = 2; 1437 case 2: 1438 if (!!(_iteratorNormalCompletion = (_step = _iterator.next()).done)) return [ 1439 3, 1440 7 1441 ]; 1442 identifier = _step.value; 1443 _state.label = 3; 1444 case 3: 1445 _state.trys.push([ 1446 3, 1447 5, 1448 , 1449 6 1450 ]); 1451 return [ 1452 4, 1453 _this.restore(identifier) 1454 ]; 1455 case 4: 1456 _state.sent(); 1457 results.success.push(identifier); 1458 return [ 1459 3, 1460 6 1461 ]; 1462 case 5: 1463 error = _state.sent(); 1464 results.errors.push({ 1465 identifier: identifier, 1466 error: _instanceof(error, Error) ? error.message :
1466String(error) 1467 }); 1468 return [ 1469 3, 1470 6 1471 ]; 1472 case 6: 1473 _iteratorNormalCompletion = true; 1474 return [ 1475 3, 1476 2 1477 ]; 1478 case 7: 1479 return [ 1480 3, 1481 10 1482 ]; 1483 case 8: 1484 err = _state.sent(); 1485 _didIteratorError = true; 1486 _iteratorError = err; 1487 return [ 1488 3, 1489 10 1490 ]; 1491 case 9: 1492 try { 1493 if (!_iteratorNormalCompletion && _iterator.return != null) { 1494 _iterator.return(); 1495 } 1496 } finally{ 1497 if (_didIteratorError) { 1498 throw _iteratorError; 1499 } 1500 } 1501 return [ 1502 7 1503 ]; 1504 case 10: 1505 return [ 1506 2, 1507 results 1508 ]; 1509 } 1510 }); 1511 })(); 1512 } 1513 }, 1514 { 1515 key: "clearFromTrash", 1516 value: /** 1517 * Permanently delete one or all entries from trash. 1518 * When called with the trash root identifier, clears the entire trash. 1519 */ function clearFromTrash(identifier) { 1520 var _this = this; 1521 return _async_to_generator(function() { 1522 return _ts_generator(this, function(_state) { 1523 switch(_state.label){ 1524 case 0: 1525 return [ 1526 4, 1527 _this.postAction(identifier, "file-clear-trash") 1528 ]; 1529 case 1: 1530 _state.sent(); 1531 // Invalidate cache 1532 _this.invalidate(identifier); 1533 return [ 1534 2, 1535 { 1536 success: true, 1537 status: "cleared" 1538 } 1539 ]; 1540 } 1541 }); 1542 })(); 1543 } 1544 }, 1545 { 1546 key: "clearFromTrashMultiple", 1547 value: /** 1548 * Permanently delete multiple entries from trash. 1549 */ function clearFromTrashMultiple(identifiers) { 1550 var _this = this; 1551 return _async_to_generator(function() { 1552 var results, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, identifier, error, err; 1553 return _ts_generator(this, function(_state) { 1554 switch(_state.label){ 1555 case 0: 1556 results = { 1557 success: [], 1558 errors: [] 1559 }; 1560 _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined; 1561 _state.label = 1; 1562 case 1: 1563 _state.trys.push([ 1564 1, 1565 8, 1566 9, 1567 10 1568 ]); 1569 _iterator = identifiers[Symbol.iterator](); 1570 _state.label = 2; 1571 case 2: 1572 if (!!(_iteratorNormalCompletion = (_step = _iterator.next()).done)) return [ 1573 3, 1574 7 1575 ]; 1576 identifier = _step.value; 1577 _state.label = 3; 1578 case 3: 1579 _state.trys.push([ 1580 3, 1581 5, 1582 , 1583 6 1584 ]); 1585 return [ 1586 4, 1587 _this.clearFromTrash(identifier) 1588 ]; 1589 case 4: 1590 _state.sent(); 1591 results.success.push(identifier); 1592 return [ 1593 3, 1594 6 1595 ]; 1596 case 5: 1597 error = _state.sent(); 1598 results.errors.push({ 1599 identifier: identifier, 1600 error: _instanceof(error, Error) ? error.message :
1600String(error) 1601 }); 1602 return [ 1603 3, 1604 6 1605 ]; 1606 case 6: 1607 _iteratorNormalCompletion = true; 1608 return [ 1609 3, 1610 2 1611 ]; 1612 case 7: 1613 return [ 1614 3, 1615 10 1616 ]; 1617 case 8: 1618 err = _state.sent(); 1619 _didIteratorError = true; 1620 _iteratorError = err; 1621 return [ 1622 3, 1623 10 1624 ]; 1625 case 9: 1626 try { 1627 if (!_iteratorNormalCompletion && _iterator.return != null) { 1628 _iterator.return(); 1629 } 1630 } finally{ 1631 if (_didIteratorError) { 1632 throw _iteratorError; 1633 } 1634 } 1635 return [ 1636 7 1637 ]; 1638 case 10: 1639 return [ 1640 2, 1641 results 1642 ]; 1643 } 1644 }); 1645 })(); 1646 } 1647 }, 1648 { 1649 key: "rename", 1650 value: /** 1651 * Rename a file or directory. 1652 * 1653 * @param identifier The opaque identifier of the file/directory to rename 1654 * @param newName The new name (not a path, just the base name) 1655 * @returns RenameResult with the new identifier and updated node data 1656 */ function rename(identifier, newName) { 1657 var _this = this; 1658 return _async_to_generator(function() { 1659 var node; 1660 return _ts_generator(this, function(_state) { 1661 switch(_state.label){ 1662 case 0: 1663 return [ 1664 4, 1665 _this.send("PATCH", _this.nodeUrl(identifier), { 1666 type: "files", 1667 attributes: { 1668 name: newName 1669 } 1670 }) 1671 ]; 1672 case 1: 1673 node = _state.sent(); 1674 // Invalidate cache for the old identifier. The new one is a 1675 // different string: an identifier encodes the path. 1676 _this.invalidate(identifier); 1677 _this.setCachedNode(node["@id"], FileSystemNode.fromData(node)); 1678 return [ 1679 2, 1680 { 1681 success: true, 1682 status: "renamed", 1683 newPath: node.path, 1684 newIdentifier: node["@id"], 1685 node: node 1686 } 1687 ]; 1688 } 1689 }); 1690 })(); 1691 } 1692 }, 1693 { 1694 key: "getFileContent", 1695 value: /** 1696 * Get the text content of a file for editing. 1697 * 1698 * The endpoint streams the file's bytes, so the body is the content itself 1699 * rather than a JSON envelope wrapping it. 1700 *
1701 * @param identifier The opaque identifier of the file to read 1702 * @returns FileContentResult with the file content and its media type 1703 */ function getFileContent(identifier) { 1704 var _this = this; 1705 return _async_to_generator(function() { 1706 var response, _, _tmp; 1707 return _ts_generator(this, function(_state) { 1708 switch(_state.label){ 1709 case 0: 1710 return [ 1711 4, 1712 fetch(_this.getDownloadUrl(identifier), { 1713 method: "GET", 1714 headers: { 1715 "Accept": "*/*" 1716 } 1717 }) 1718 ]; 1719 case 1: 1720 response = _state.sent(); 1721 // Check for authentication failure before processing response 1722 if (_this.handleAuthenticationError(response)) { 1723 throw new Error("Session expired"); 1724 } 1725 if (!!response.ok) return [ 1726 3, 1727 3 1728 ]; 1729 _ = Error.bind; 1730 return [ 1731 4, 1732 _this.readErrorMessage(response) 1733 ]; 1734 case 2: 1735 throw new (_.apply(Error, [ 1736 void 0, 1737 _state.sent() 1738 ])); 1739 case 3: 1740 _tmp = {}; 1741 return [ 1742 4, 1743 response.text() 1744 ]; 1745 case 4: 1746 return [ 1747 2, 1748 (_tmp.content = _state.sent(), _tmp.mediaType = (response.headers.get("Content-Type") || "").split(";")[0].trim(), _tmp) 1749 ]; 1750 } 1751 }); 1752 })(); 1753 } 1754 }, 1755 { 1756 key: "readErrorMessage", 1757 value: /** 1758 * Extract an error message from a failed response. 1759 * 1760 * Every failure under `/api/v1` is a JSON:API `errors` document â 1761 * including from the byte-transfer routes, which are outside the 1762 * codec for their *successes* only. Only the first error is 1763 * reported: these messages end up in a toast, and a validation 1764 * failure with one violation per field would be an unreadable wall. 1765 * 1766 * The text fallback is not dead code. A response that never reached 1767 * the application â an edge 502, a login page from an expired 1768 * session on a route that does not 401 â is not JSON at all. 1769 */ function readErrorMessage(response) { 1770 return _async_to_generator(function() { 1771 var fallback, _data_errors, body, data, first, e; 1772 return _ts_generator(this, function(_state) { 1773 switch(_state.label){ 1774 case 0: 1775 fallback = "HTTP ".concat(response.status, ": ").concat(response.statusText); 1776 _state.label = 1; 1777 case 1: 1778 _state.trys.push([ 1779 1, 1780 3, 1781 , 1782 4 1783 ]); 1784 return [ 1785 4, 1786 response.text() 1787 ]; 1788 case 2: 1789 body = _state.sent(); 1790 data = JSON.parse(body); 1791 first = (_data_errors = data.errors) === null || _data_errors === void 0 ? void 0 : _data_errors[0]; 1792 if (typeof (first === null || first === void 0 ? void 0 : first.detail) === "string") { 1793 return [ 1794 2, 1795 first.detail 1796 ]; 1797 } 1798 return [ 1799 2, 1800 typeof (first === null || first === void 0 ? void 0 : first.title) === "string" ? first.title : fallback 1801 ]; 1802 case 3: 1803 e = _state.sent(); 1804 return [ 1805 2, 1806 fallback 1807 ]; 1808 case 4: 1809 return [ 1810 2 1811 ]; 1812 } 1813 }); 1814 })(); 1815 } 1816 }, 1817 { 1818 key: "saveFileContent", 1819 value: /** 1820 * Save the text content of a file. 1821 *
1822 * @param identifier The opaque identifier of the file to save 1823 * @param content The new text content 1824 * @returns SaveContentResult indicating success, including updated node data 1825 */ function saveFileContent(identifier, content) { 1826 var _this = this; 1827 return _async_to_generator(function() { 1828 var response, _; 1829 return _ts_generator(this, function(_state) { 1830 switch(_state.label){ 1831 case 0: 1832 return [ 1833 4, 1834 fetch(_this.nodeUrl(identifier, "/content"), { 1835 method: "PUT", 1836 headers: { 1837 "Content-Type": "application/octet-stream" 1838 }, 1839 body: content 1840 }) 1841 ]; 1842 case 1: 1843 response = _state.sent(); 1844 // Check for authentication failure before processing response 1845 if (_this.handleAuthenticationError(response)) { 1846 throw new Error("Session expired"); 1847 } 1848 if (!!response.ok) return [ 1849 3, 1850 3 1851 ]; 1852 _ = Error.bind; 1853 return [ 1854 4, 1855 _this.readErrorMessage(response) 1856 ]; 1857 case 2: 1858 throw new (_.apply(Error, [ 1859 void 0, 1860 _state.sent() 1861 ])); 1862 case 3: 1863 // A 204: the write does not answer with the node. Its size and 1864 // its modification time both just changed, so the cached copy 1865 // is stale and is dropped â the caller re-reads it if it needs 1866 // to show either. Re-`stat`ing on every save to describe a 1867 // write the caller performed would be a round trip for 1868 // something nobody always looks at. 1869 _this.invalidate(identifier); 1870 return [ 1871 2, 1872 { 1873 success: true 1874 } 1875 ]; 1876 } 1877 }); 1878 })(); 1879 } 1880 }, 1881 { 1882 key: "createDirectory", 1883 value: /** 1884 * Create a new directory inside a parent directory. 1885 * 1886 * @param parentIdentifier The opaque identifier of the parent directory 1887 * @param name The name for the new directory 1888 * @returns CreateDirectoryResult with the new directory data 1889 */ function createDirectory(parentIdentifier, name) { 1890 var _this = this; 1891 return _async_to_generator(function() { 1892 return _ts_generator(this, function(_state) { 1893 switch(_state.label){ 1894 case 0: 1895 return [ 1896 4, 1897 _this.create(parentIdentifier, name, "directories") 1898 ]; 1899 case 1: 1900 return [ 1901 2, 1902 _state.sent() 1903 ]; 1904 } 1905 }); 1906 })(); 1907 } 1908 }, 1909 { 1910 key: "createFile", 1911 value: /** 1912 * Create a new empty file in a directory. 1913 *
1914 * @param parentIdentifier The opaque identifier of the parent directory 1915 * @param name The name of the file to create 1916 * @returns CreateFileResult with the new file data 1917 */ function createFile(parentIdentifier, name) { 1918 var _this = this; 1919 return _async_to_generator(function() { 1920 return _ts_generator(this, function(_state) { 1921 switch(_state.label){ 1922 case 0: 1923 return [ 1924 4, 1925 _this.create(parentIdentifier, name, "files") 1926 ]; 1927 case 1: 1928 return [ 1929 2, 1930 _state.sent() 1931 ]; 1932 } 1933 }); 1934 })(); 1935 } 1936 }, 1937 { 1938 key: "create", 1939 value: /** 1940 * Create one node in a directory. 1941 * 1942 * `mkdir` and `touch` were two endpoints; they are one now, and the 1943 * *posted resource type* is what chooses the kind. The containing 1944 * directory is a relationship rather than a path segment, which is 1945 * what makes this a create of a `files` resource rather than an 1946 * action on the parent. 1947 */ function create(parentIdentifier, name, type) { 1948 var _this = this; 1949 return _async_to_generator(function() { 1950 var node; 1951 return _ts_generator(this, function(_state) { 1952 switch(_state.label){ 1953 case 0: 1954 return [ 1955 4, 1956 _this.send("POST", "/api/v1/files", { 1957 type: type, 1958 attributes: { 1959 name: name 1960 }, 1961 relationships: { 1962 parent: { 1963 data: { 1964 type: "directories", 1965 id: parentIdentifier 1966 } 1967 } 1968 } 1969 }) 1970 ]; 1971 case 1: 1972 node = _state.sent(); 1973 // Invalidate cache for the parent directory (new child added) 1974 _this.invalidate(parentIdentifier); 1975 _this.setCachedNode(node["@id"], FileSystemNode.fromData(node)); 1976 return [ 1977 2, 1978 { 1979 success: true, 1980 status: "created", 1981 path: node.path, 1982 identifier: node["@id"], 1983 node: node 1984 } 1985 ]; 1986 } 1987 }); 1988 })(); 1989 } 1990 }, 1991 { 1992 key: "copyMultiple", 1993 value: /** 1994 * Copy multiple files/directories to a target directory. 1995 * 1996 * @param sourceIdentifiers Array of source opaque identifiers to copy 1997 * @param targetIdentifier The opaque identifier of the target directory 1998 * @returns CopyMoveMultipleResult with results for each source 1999 */ function copyMultiple(sourceIdentifiers, targetIdentifier) { 2000 var _this = this; 2001 return _async_to_generator(function() { 2002 return _ts_generator(this, function(_state) { 2003 switch(_state.label){ 2004 case 0: 2005 return [ 2006 4, 2007 _this.transfer("file-copy-from", sourceIdentifiers, targetIdentifier) 2008 ]; 2009 case 1: 2010 return [ 2011 2, 2012 _state.sent() 2013 ]; 2014 } 2015 }); 2016 })(); 2017 } 2018 }, 2019 { 2020 key: "moveMultiple", 2021 value: /** 2022 * Move multiple files/directories to a target directory. 2023 * 2024 * @param sourceIdentifiers Array of source opaque identifiers to move
2025 * @param targetIdentifier The opaque identifier of the target directory 2026 * @returns CopyMoveMultipleResult with results for each source 2027 */ function moveMultiple(sourceIdentifiers, targetIdentifier) { 2028 var _this = this; 2029 return _async_to_generator(function() { 2030 return _ts_generator(this, function(_state) { 2031 switch(_state.label){ 2032 case 0: 2033 return [ 2034 4, 2035 _this.transfer("file-move-from", sourceIdentifiers, targetIdentifier) 2036 ]; 2037 case 1: 2038 return [ 2039 2, 2040 _state.sent() 2041 ]; 2042 } 2043 }); 2044 })(); 2045 } 2046 }, 2047 { 2048 key: "transfer", 2049 value: /** 2050 * Bring a batch of nodes into a directory, by copy or by move. 2051 * 2052 * Batch on purpose: a multi-selection paste is one request, and one 2053 * source failing (a name already taken at the destination) reports 2054 * against that source and leaves the rest of the batch alone. 2055 * Splitting it into N requests would lose the partial-failure 2056 * report as well as costing N round trips. 2057 */ function transfer(action, sourceIdentifiers, targetIdentifier) { 2058 var _this = this; 2059 return _async_to_generator(function() { 2060 var report, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, sourceIdentifier; 2061 return _ts_generator(this, function(_state) { 2062 switch(_state.label){ 2063 case 0: 2064 return [ 2065 4, 2066 _this.postAction(targetIdentifier, action, { 2067 sources: sourceIdentifiers 2068 }) 2069 ]; 2070 case 1: 2071 report = _state.sent(); 2072 // Invalidate the target (new children) and, for a move, every 2073 // source: it is not where it was. 2074 _this.invalidate(targetIdentifier); 2075 if (action === "file-move-from") { 2076 _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined; 2077 try { 2078 for(_iterator = sourceIdentifiers[Symbol.iterator](); !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){ 2079 sourceIdentifier = _step.value; 2080 _this.invalidate(sourceIdentifier); 2081 } 2082 } catch (err) { 2083 _didIteratorError = true; 2084 _iteratorError = err; 2085 } finally{ 2086 try { 2087 if (!_iteratorNormalCompletion && _iterator.return != null) { 2088 _iterator.return(); 2089 } 2090 } finally{ 2091 if (_didIteratorError) { 2092 throw _iteratorError; 2093 } 2094 } 2095 } 2096 } 2097 return [ 2098 2, 2099 { 2100 results: report.results.map(function(item) { 2101 var _item_node, _item_node1; 2102 if (item.node) { 2103 _this.setCachedNode(item.node["@id"], FileSystemNode.fromData(item.node)); 2104 } 2105 var _item_node2, _item_error; 2106 return { 2107 success: item.transferred, 2108 sourceIdentifier: item.source, 2109 status: item.transferred ? action === "file-move-from" ? "moved" : "copied" : "error", 2110 destinationPath: (_item_node = item.node) === null || _item_node === void 0 ? void 0 : _item_node.path, 2111 destinationIdentifier: (_item_node1 = item.node) === null || _item_node1 === void 0 ? void 0 : _item_node1["@id"], 2112 node: (_item_node2 = item.node) !== null && _item_node2 !== void 0 ? _item_node2 : undefined, 2113 error: (_item_error = item.error) !== null && _item_error !== void 0 ? _item_error : undefined 2114 }; 2115 }) 2116 } 2117 ]; 2118 } 2119 }); 2120 })(); 2121 } 2122 }, 2123 { 2124 key: "changeWebPermission", 2125 value: /** 2126 * Change the web permission of a node. 2127 * 2128 * @param identifier - The node identifier 2129 * @param permission - The permission to set: 'none', 'read-only', or 'read-write' 2130 * @param recursive - Whether to apply recursively to all children 2131 * @returns Promise with the result including updated node data 2132 */ function changeWebPermission(identifier, permission, recursive) { 2133 var _this = this; 2134 return _async_to_generator(function() { 2135 var node, error; 2136 return _ts_generator(this, function(_state) { 2137 switch(_state.label){ 2138 case 0: 2139 _state.trys.push([ 2140 0, 2141 2, 2142 , 2143 3 2144 ]); 2145 return [ 2146 4, 2147 _this.postAction(identifier, "file-set-web-permission", { 2148 permission: permission, 2149 recursive: recursive 2150 }) 2151 ]; 2152 case 1: 2153 node = _state.sent(); 2154 // The mode changed, so the cached copy is stale; replace it 2155 // with the one the write answered rather than dropping it. 2156 _this.setCachedNode(identifier, FileSystemNode.fromData(node)); 2157 return [ 2158 2, 2159 { 2160 success: true, 2161 node: node 2162 } 2163 ]; 2164 case 2: 2165 error = _state.sent(); 2166 if (_instanceof(error, Error) && error.message === "Session expired") { 2167 throw error; 2168 } 2169 return [ 2170 2, 2171 { 2172 success: false, 2173 error: _instanceof(error, Error) ? error.message :
2173String(error) 2174 } 2175 ]; 2176 case 3: 2177 return [ 2178 2 2179 ]; 2180 } 2181 }); 2182 })(); 2183 } 2184 }, 2185 { 2186 key: "recalculateSize", 2187 value: /** 2188 * Recalculate the size of a directory. 2189 * 2190 * @param identifier - The directory identifier 2191 * @returns Promise with the recalculated size 2192 */ function recalculateSize(identifier) { 2193 var _this = this; 2194 return _async_to_generator(function() { 2195 var node, _node_size, error; 2196 return _ts_generator(this, function(_state) { 2197 switch(_state.label){ 2198 case 0: 2199 _state.trys.push([ 2200 0, 2201 2, 2202 , 2203 3 2204 ]); 2205 return [ 2206 4, 2207 _this.postAction(identifier, "file-recalculate-size") 2208 ]; 2209 case 1: 2210 node = _state.sent(); 2211 // Invalidate cache for this node so it gets refreshed 2212 _this.invalidate(identifier); 2213 return [ 2214 2, 2215 { 2216 success: true, 2217 size: (_node_size = node.size) !== null && _node_size !== void 0 ? _node_size : 0 2218 } 2219 ]; 2220 case 2: 2221 error = _state.sent(); 2222 if (_instanceof(error, Error) && error.message === "Session expired") { 2223 throw error; 2224 } 2225 return [ 2226 2, 2227 { 2228 success: false, 2229 size: 0, 2230 error: _instanceof(error, Error) ? error.message : String(error) 2231 } 2232 ]; 2233 case 3: 2234 return [ 2235 2 2236 ]; 2237 } 2238 }); 2239 })(); 2240 } 2241 }, 2242 { 2243 key: "archive", 2244 value: /** 2245 * Create an archive from selected files/directories. 2246 * 2247 * @param directoryIdentifier The opaque identifier of the directory containing the items 2248 * @param format The archive format (tar.gz, zip, 7z, etc.) 2249 * @param items Array of basenames to include in the archive 2250 * @returns ArchiveResult with the created archive node 2251 */ function archive(directoryIdentifier, format, items) { 2252 var _this = this; 2253 return _async_to_generator(function() { 2254 var node, error; 2255 return _ts_generator(this, function(_state) { 2256 switch(_state.label){ 2257 case 0: 2258 _state.trys.push([ 2259 0, 2260 2, 2261 , 2262 3 2263 ]); 2264 return [ 2265 4, 2266 _this.postAction(directoryIdentifier, "file-archive", { 2267 format: format, 2268 items: items 2269 }) 2270 ]; 2271 case 1: 2272 node = _state.sent(); 2273 // Invalidate directory cache to refresh listing 2274 _this.invalidate(directoryIdentifier); 2275 return [ 2276 2, 2277 { 2278 success: true, 2279 status: "created", 2280 node: node 2281 } 2282 ]; 2283 case 2: 2284 error = _state.sent(); 2285 if (_instanceof(error, Error) && error.message === "Session expired") { 2286 throw error; 2287 } 2288 return [ 2289 2, 2290 { 2291 success: false, 2292 status: "error", 2293 error: _instanceof(error, Error) ? error.message :
2293String(error) 2294 } 2295 ]; 2296 case 3: 2297 return [ 2298 2 2299 ]; 2300 } 2301 }); 2302 })(); 2303 } 2304 }, 2305 { 2306 /** 2307 * Download an archive stream from selected files/directories without creating a file on disk. 2308 * 2309 * @param directoryIdentifier The opaque identifier of the directory containing the items 2310 * @param format The archive format (tar.gz, zip, 7z, etc.) 2311 * @param items Array of basenames to include in the archive 2312 * @returns void - triggers browser download 2313 */ key: "archiveDownload", 2314 value: function archiveDownload(directoryIdentifier, format, items) { 2315 var _this = this; 2316 var _this1 = this; 2317 // Use fetch to get the blob, then create a download link. 2318 // A plain JSON body, not a JSON:API envelope: this route is one 2319 // of the byte transfers, which sit outside the codec. 2320 fetch(this.nodeUrl(directoryIdentifier, "/content/archive"), { 2321 method: "POST", 2322 headers: { 2323 "Accept": "application/octet-stream", 2324 "Content-Type": "application/json" 2325 }, 2326 body: JSON.stringify({ 2327 format: format, 2328 items: items 2329 }) 2330 }).then(function() { 2331 var _ref = _async_to_generator(function(response) { 2332 var _, contentDisposition, filename, filenameMatch; 2333 return _ts_generator(this, function(_state) { 2334 switch(_state.label){ 2335 case 0: 2336 if (!!response.ok) return [ 2337 3, 2338 2 2339 ]; 2340 _ = Error.bind; 2341 return [ 2342 4, 2343 _this1.readErrorMessage(response) 2344 ]; 2345 case 1: 2346 throw new (_.apply(Error, [ 2347 void 0, 2348 _state.sent() 2349 ])); 2350 case 2: 2351 // Get the filename from Content-Disposition header 2352 contentDisposition = response.headers.get("Content-Disposition"); 2353 filename = "archive.".concat(format); 2354 if (contentDisposition) { 2355 filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/); 2356 if (filenameMatch && filenameMatch[1]) { 2357 filename = filenameMatch[1].replace(/['"]/g, ""); 2358 } 2359 } 2360 // Convert response to blob 2361 return [ 2362 2, 2363 response.blob().then(function(blob) { 2364 return { 2365 blob: blob, 2366 filename: filename 2367 }; 2368 }) 2369 ]; 2370 } 2371 }); 2372 }); 2373 return function(response) { 2374 return _ref.apply(this, arguments); 2375 }; 2376 }()).then(function(param) { 2377 var blob = param.blob, filename = param.filename; 2378 // Create a download link and trigger it 2379 var url = window.URL.createObjectURL(blob); 2380 var a = document.createElement("a"); 2381 a.style.display = "none"; 2382 a.href = url; 2383 a.download = filename; 2384 document.body.appendChild(a); 2385 a.click(); 2386 window.URL.revokeObjectURL(url); 2387 document.body.removeChild(a); 2388 }).catch(function(error) { 2389 console.error("Failed to download archive:", error); 2390 // Dispatch error event 2391 if (_this.config.eventTarget) { 2392 _this.config.eventTarget.dispatchEvent(new CustomEvent("fs:error", { 2393 detail: { 2394 error: error.message 2395 } 2396 })); 2397 } 2398 }); 2399 } 2400 }, 2401 { 2402 key: "extract", 2403 value: /** 2404 * Extract an archive to current directory or a new folder. 2405 *
2406 * @param fileIdentifier The opaque identifier of the archive file 2407 * @param mode Extract mode: 'here' or 'folder' 2408 * @returns ExtractResult with the extracted nodes 2409 */ function extract(fileIdentifier, mode) { 2410 var _this = this; 2411 return _async_to_generator(function() { 2412 var extracted, error; 2413 return _ts_generator(this, function(_state) { 2414 switch(_state.label){ 2415 case 0: 2416 _state.trys.push([ 2417 0, 2418 2, 2419 , 2420 3 2421 ]); 2422 return [ 2423 4, 2424 _this.postAction(fileIdentifier, "file-extract", { 2425 mode: mode 2426 }) 2427 ]; 2428 case 1: 2429 extracted = _state.sent(); 2430 // Invalidate file cache and parent to refresh listing 2431 _this.invalidate(fileIdentifier); 2432 return [ 2433 2, 2434 { 2435 success: true, 2436 status: "extracted", 2437 nodes: extracted.items 2438 } 2439 ]; 2440 case 2: 2441 error = _state.sent(); 2442 if (_instanceof(error, Error) && error.message === "Session expired") { 2443 throw error; 2444 } 2445 return [ 2446 2, 2447 { 2448 success: false, 2449 status: "error", 2450 nodes: [], 2451 error: _instanceof(error, Error) ? error.message : String(error) 2452 } 2453 ]; 2454 case 3: 2455 return [ 2456 2 2457 ]; 2458 } 2459 }); 2460 })(); 2461 } 2462 }, 2463 { 2464 /** 2465 * Clear the cache 2466 */ key: "clearCache", 2467 value: function clearCache() { 2468 if (this.config.stateController) { 2469 this.config.stateController.clearCache(); 2470 } else { 2471 this.cache.clear(); 2472 } 2473 } 2474 }, 2475 { 2476 /** 2477 * Remove a specific identifier from cache 2478 */ key: "invalidate",
2479 value: function invalidate(identifier) { 2480 if (this.config.stateController) { 2481 this.config.stateController.invalidateNode(identifier); 2482 } else { 2483 this.cache.delete(identifier); 2484 } 2485 } 2486 }, 2487 { 2488 /** 2489 * Cancel all pending requests 2490 */ key: "cancelAll", 2491 value: function cancelAll() { 2492 this.abortControllers.forEach(function(controller) { 2493 return controller.abort(); 2494 }); 2495 this.abortControllers.clear(); 2496 } 2497 }, 2498 { 2499 /** 2500 * Get cached node if available 2501 */ key: "getCached", 2502 value: function getCached(identifier) { 2503 return this.getCachedNode(identifier); 2504 } 2505 }, 2506 { 2507 key: "rootIdentifier", 2508 get: /** 2509 * Get the root identifier for this filesystem 2510 */ function get() { 2511 return this.config.rootIdentifier; 2512 } 2513 } 2514 ]); 2515 return FileSystemService; 2516}(); 2517/** 2518 * The flat representation. See the class docblock. 2519 */ _define_property(FileSystemService, "FLAT", "application/jsonl"); 2520/** 2521 * The JSON:API media type, for request bodies. 2522 */ _define_property(FileSystemService, "JSON_API", "application/vnd.api+json");
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.