@statewalker/webrun-files 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // @statewalker/webrun-files v0.1.0 https://github.com/statewalker/webrun-files Copyright 2023 Mikhail Kotelnikov
1
+ // @statewalker/webrun-files v0.3.0 https://github.com/statewalker/webrun-files Copyright 2023 Mikhail Kotelnikov
2
2
  function normalizePath(filePath) {
3
3
  const segments = filePath.split("/").filter((s) => !!s && s !== ".");
4
4
  if (segments.length === 0) {
@@ -240,7 +240,6 @@ const mimeTypeToExtensions = mimeTypes.reduce((index, t) => {
240
240
  }, {});
241
241
 
242
242
  // import mimeDb from 'mime-db';
243
- // import mimeTypes from 'mime-types';
244
243
 
245
244
  function getMimeType(url) {
246
245
  url = url || '';
@@ -605,30 +604,165 @@ async function* list(fs, filePath, recursive) {
605
604
  }
606
605
  }
607
606
 
607
+ /**
608
+ * Stateless adapter for the "File and Directory Entries API".
609
+ * See https://developer.mozilla.org/en-US/docs/Web/API/File_and_Directory_Entries_API
610
+ *
611
+ * ## File and Directory Entries API
612
+ * The File and Directory Entries API simulates a local file system
613
+ * this web apps can navigate within and access files in. You can
614
+ * develop apps which read, write, and create files and/or directories
615
+ * in a virtual, sandboxed file system.
616
+ */
617
+ class AdapterForFilesAndDirectoryEntriesApi {
618
+ static canApply(handle) {
619
+ return ((handle?.isDirectory || handle?.isFile) &&
620
+ (typeof handle?.name === "string"));
621
+ }
622
+
623
+ async *listDirectoryChildren(handle) {
624
+ let dirReader = handle.createReader();
625
+ while (true) {
626
+ const list = await new Promise((resolve, reject) =>
627
+ dirReader.readEntries(resolve, reject)
628
+ );
629
+ if (!list.length) break;
630
+ yield* list;
631
+ }
632
+ }
633
+
634
+ isDirectoryHandle(handle) {
635
+ return handle?.isDirectory;
636
+ }
637
+ isFileHandle(handle) {
638
+ return handle?.isFile;
639
+ }
640
+
641
+ async getSubdirectoryHandle(dirHandle, directoryName, create) {
642
+ return new Promise((resolve, reject) =>
643
+ dirHandle.getDirectory(
644
+ directoryName,
645
+ { create },
646
+ resolve,
647
+ reject,
648
+ )
649
+ );
650
+ }
651
+
652
+ async getFileHandle(dirHandle, fileName, create = true) {
653
+ return new Promise((resolve, reject) =>
654
+ dirHandle.getFile(
655
+ fileName,
656
+ { create },
657
+ resolve,
658
+ reject,
659
+ )
660
+ );
661
+ }
662
+
663
+ async removeDirectoryEntry(dirHandle, handle) {
664
+ return new Promise((resolve, reject) => handle.remove(resolve, reject));
665
+ }
666
+
667
+ async getFile(handle) {
668
+ const file = await new Promise((resolve, reject) =>
669
+ handle.file(resolve, reject)
670
+ );
671
+ return file;
672
+ }
673
+
674
+ async getFileWriteStream(handle) {
675
+ throw new Error("Not implemented");
676
+ }
677
+ }
678
+
679
+ /**
680
+ * Stateless adapter for the "File System API" and "File System Access API".
681
+ * See:
682
+ * - https://developer.mozilla.org/en-US/docs/Web/API/File_System_API
683
+ * - https://wicg.github.io/file-system-access/
684
+ */
685
+ class AdapterForFileSystemApi {
686
+ static canApply(handle) {
687
+ return (handle?.kind === "file" || handle?.kind === "directory");
688
+ }
689
+
690
+ async *listDirectoryChildren(handle) {
691
+ yield* handle.values();
692
+ }
693
+
694
+ isDirectoryHandle(handle) {
695
+ return handle?.kind === "directory";
696
+ }
697
+ isFileHandle(handle) {
698
+ return handle?.kind === "file";
699
+ }
700
+
701
+ async getSubdirectoryHandle(dirHandle, directoryName, create) {
702
+ return await dirHandle.getDirectoryHandle(directoryName, { create });
703
+ }
704
+
705
+ async getFileHandle(dirHandle, fileName, create = true) {
706
+ return await dirHandle.getFileHandle(fileName, { create });
707
+ }
708
+
709
+ async removeDirectoryEntry(dirHandle, handle) {
710
+ return await dirHandle.removeEntry(handle.name, { recursive: true });
711
+ }
712
+
713
+ async getFile(handle) {
714
+ return await handle.getFile();
715
+ }
716
+
717
+ async getFileWriteStream(handle) {
718
+ return await handle.createWritable(); // Write the contents of the file to the stream.
719
+ }
720
+ }
721
+
608
722
  class BrowserFilesApi extends FilesApi {
609
723
  constructor(options) {
610
724
  super(options);
611
- if (!this.rootHandler) {
612
- throw new Error("Root directory handler is not defined");
725
+ if (!this.rootHandle) {
726
+ throw new Error("Root directory handle is not defined");
727
+ }
728
+ if (!this.adapter) {
729
+ throw new Error(
730
+ "Can not define a browser adapter for the specified root handle",
731
+ );
613
732
  }
614
733
  }
615
734
 
616
- get rootHandler() {
617
- return this.options.rootHandler;
735
+ get rootHandle() {
736
+ return this.options.rootHandle || this.options.rootHandler;
737
+ }
738
+
739
+ get adapter() {
740
+ if (!this._adapter) {
741
+ const handle = this.rootHandle;
742
+ let adapter = AdapterForFilesAndDirectoryEntriesApi.canApply(handle)
743
+ ? new AdapterForFilesAndDirectoryEntriesApi()
744
+ : AdapterForFileSystemApi.canApply(handle)
745
+ ? new AdapterForFileSystemApi()
746
+ : null;
747
+ this._adapter = adapter;
748
+ }
749
+ return this._adapter;
618
750
  }
619
751
 
620
752
  async *list(file, { recursive = false } = {}) {
621
753
  const segments = this._getPathSegments(file);
622
- const handle = await this._getHandler(segments, false);
754
+ const handle = await this._getHandle(segments);
755
+ if (!handle) return;
623
756
  const that = this;
624
- if (handle.kind === "directory") {
757
+ if (this.adapter.isDirectoryHandle(handle)) {
758
+ // yield await this._getFileInfo(segments, handle);
625
759
  yield* listChildren(segments, handle, recursive);
626
760
  }
627
761
 
628
762
  async function* listChildren(segments, dirHandle, recursive) {
629
- for await (const handle of dirHandle.values()) {
763
+ for await (const handle of that._listDirectoryContent(dirHandle)) {
630
764
  yield await that._getFileInfo([...segments, handle.name], handle);
631
- if (recursive && handle.kind == "directory") {
765
+ if (recursive && that.adapter.isDirectoryHandle(handle)) {
632
766
  yield* listChildren(
633
767
  [...segments, handle.name],
634
768
  handle,
@@ -641,32 +775,45 @@ class BrowserFilesApi extends FilesApi {
641
775
 
642
776
  async stats(file) {
643
777
  const segments = this._getPathSegments(file);
644
- const handle = await this._getHandler(segments, false);
778
+ const handle = await this._getHandle(segments);
645
779
  return handle ? await this._getFileInfo(segments, handle) : null;
646
780
  }
647
781
 
648
782
  async remove(file) {
649
- let segments = this._getPathSegments(file);
650
- const name = segments.pop();
651
- const parentHandle = await this._getHandler(segments, false);
652
- const toRemove = [];
653
- if (name) {
654
- toRemove.push(name);
655
- } else {
656
- for await (const childHandle of parentHandle.values()) {
657
- toRemove.push(childHandle.name);
783
+ const doRemove = async (dirHandle, handle) => {
784
+ let result = true;
785
+ if (this.adapter.isDirectoryHandle(handle)) {
786
+ const children = [];
787
+ for await (let child of this.adapter.listDirectoryChildren(handle)) {
788
+ children.push(child);
789
+ }
790
+ for (let child of children) {
791
+ result = result && await doRemove(handle, child);
792
+ }
658
793
  }
659
- }
660
- for (const name of toRemove) {
661
- await parentHandle.removeEntry(name, { recursive: true });
662
- }
663
- return true;
794
+ if (dirHandle) {
795
+ result &= await this.adapter.removeDirectoryEntry(dirHandle, handle);
796
+ }
797
+ return result;
798
+ };
799
+
800
+ const segments = this._getPathSegments(file);
801
+ const handle = await this._getHandle(segments);
802
+ segments.pop();
803
+ const dirHandle = await this._getHandle(segments);
804
+ return doRemove(dirHandle !== handle ? dirHandle : null, handle);
664
805
  }
665
806
 
666
807
  async write(file, content) {
667
808
  const segments = this._getPathSegments(file);
668
- const fileHandle = await this._getFileHandler(segments);
669
- const writable = await fileHandle.createWritable(); // Write the contents of the file to the stream.
809
+ const fileName = segments.pop();
810
+ const dirHandle = await this._getDirectoryHandle(segments, true);
811
+ const fileHandle = await this.adapter.getFileHandle(
812
+ dirHandle,
813
+ fileName,
814
+ true,
815
+ );
816
+ const writable = await this.adapter.getFileWriteStream(fileHandle);
670
817
  try {
671
818
  if (typeof content === "function") content = content();
672
819
  for await (let block of content) {
@@ -679,18 +826,26 @@ class BrowserFilesApi extends FilesApi {
679
826
 
680
827
  async *read(file /* { start = 0, bufferSize = 1024 * 8 } = {} */) {
681
828
  const segments = this._getPathSegments(file);
682
- const fileHandle = await this._getFileHandler(segments, false);
829
+
830
+ const fileName = segments.pop();
831
+ const dirHandle = await this._getDirectoryHandle(segments, false);
832
+ const fileHandle = await this.adapter.getFileHandle(
833
+ dirHandle,
834
+ fileName,
835
+ false,
836
+ );
683
837
  if (!fileHandle) return;
684
- const fileData = await fileHandle.getFile();
838
+ const fileData = await this.adapter.getFile(fileHandle);
685
839
  const stream = fileData.stream();
686
840
  const reader = stream.getReader();
687
841
  try {
688
842
  let chunk;
689
- while ((chunk = await reader.read()) && !chunk.done) {
690
- yield chunk.value;
843
+ while (chunk = await reader.read()) {
844
+ if (chunk.done) break;
845
+ yield await chunk.value;
691
846
  }
692
847
  } finally {
693
- reader.cancel && reader.cancel();
848
+ await reader.cancel && reader.cancel();
694
849
  // stream.cancel && stream.cancel();
695
850
  }
696
851
  }
@@ -701,41 +856,8 @@ class BrowserFilesApi extends FilesApi {
701
856
  // TODO:
702
857
  // async move(fromPath, toPath, options = {}) { ... }
703
858
 
704
- async _getHandler(segments, create = false) {
705
- let handle;
706
- try {
707
- handle = await this._getFileHandler(segments, create);
708
- } catch (e) {
709
- try {
710
- handle = await this._getDirectoryHandler(segments, create);
711
- } catch (e) {
712
- // Just ignore it
713
- }
714
- }
715
- return handle;
716
- }
717
-
718
- async _getDirectoryHandler(segments, create = true) {
719
- segments = [...segments];
720
- let dirHandle = this.rootHandler;
721
- for (let directoryName of segments) {
722
- dirHandle = await dirHandle.getDirectoryHandle(directoryName, { create });
723
- }
724
- return dirHandle;
725
- }
726
-
727
- async _getFileHandler(segments, create = true) {
728
- segments = [...segments];
729
- const filename = segments.pop();
730
- let dirHandle = this.rootHandler;
731
- for (let directoryName of segments) {
732
- dirHandle = await dirHandle.getDirectoryHandle(directoryName, {
733
- create: true,
734
- });
735
- }
736
- const fileHandler = await dirHandle.getFileHandle(filename, { create });
737
- return fileHandler;
738
- }
859
+ // ---------------------------------------------------------
860
+ // Private methods
739
861
 
740
862
  async _getFileInfo(segments, handle) {
741
863
  segments = [...segments];
@@ -747,11 +869,12 @@ class BrowserFilesApi extends FilesApi {
747
869
  path,
748
870
  name,
749
871
  };
750
- if (handle.kind === "directory") {
872
+ if (this.adapter.isDirectoryHandle(handle)) {
751
873
  info.kind = "directory";
752
874
  } else {
753
875
  info.kind = "file";
754
- const file = await handle.getFile();
876
+
877
+ const file = await this.adapter.getFile(handle);
755
878
  info.size = file.size;
756
879
  info.type = getMimeType(path);
757
880
  info.lastModified = file.lastModified;
@@ -765,6 +888,53 @@ class BrowserFilesApi extends FilesApi {
765
888
  const segments = filePath.split("/").filter((s) => !!s);
766
889
  return segments;
767
890
  }
891
+
892
+ async _getHandle(segments) {
893
+ const parentDirSegments = [...segments];
894
+ const name = parentDirSegments.pop();
895
+ const create = false;
896
+ try {
897
+ const dirHandle = await this._getDirectoryHandle(
898
+ parentDirSegments,
899
+ create,
900
+ );
901
+ try {
902
+ if (!name) return dirHandle;
903
+ return await this.adapter.getSubdirectoryHandle(
904
+ dirHandle,
905
+ name,
906
+ create,
907
+ );
908
+ } catch (error) {
909
+ return await this.adapter.getFileHandle(dirHandle, name, create);
910
+ }
911
+ } catch (error) {
912
+ return undefined;
913
+ }
914
+ }
915
+
916
+ async _getDirectoryHandle(segments, create = true) {
917
+ segments = [...segments];
918
+ let dirHandle = this.rootHandle;
919
+ for (let directoryName of segments) {
920
+ dirHandle = await this.adapter.getSubdirectoryHandle(
921
+ dirHandle,
922
+ directoryName,
923
+ create,
924
+ );
925
+ if (!dirHandle) break;
926
+ }
927
+ return dirHandle;
928
+ }
929
+
930
+ async *_listDirectoryContent(handle) {
931
+ let entries = [];
932
+ for await (const entry of this.adapter.listDirectoryChildren(handle)) {
933
+ entries.push(entry);
934
+ }
935
+ entries = entries.sort((a, b) => b.name > a.name ? 1 : -1);
936
+ yield* entries;
937
+ }
768
938
  }
769
939
 
770
940
  function addFilesApiLogger({
@@ -817,46 +987,46 @@ async function openBrowserFilesApi({
817
987
  throw new Error("Can not access to the folder. Please try again.");
818
988
  }
819
989
  return new BrowserFilesApi({ rootHandler });
990
+ }
820
991
 
821
- async function isHandlerAccessible(fileHandle) {
822
- // We need to perform a real read operation with the handler
823
- // to check if the file/directory still exists.
824
- let exists = false;
825
- try {
826
- if (fileHandle.kind === "file") {
827
- await fileHandle.getFile();
828
- } else {
829
- for await (let item of fileHandle.values()) {
830
- break;
831
- }
992
+ async function isHandlerAccessible(fileHandle) {
993
+ // We need to perform a real read operation with the handler
994
+ // to check if the file/directory still exists.
995
+ let exists = false;
996
+ try {
997
+ if (fileHandle.kind === "file") {
998
+ await fileHandle.getFile();
999
+ } else {
1000
+ for await (let item of fileHandle.values()) {
1001
+ break;
832
1002
  }
833
- exists = true;
834
- } catch (error) {
835
- console.log(error, fileHandle);
836
- exists = false;
837
1003
  }
838
- return exists;
1004
+ exists = true;
1005
+ } catch (error) {
1006
+ console.log(error, fileHandle);
1007
+ exists = false;
839
1008
  }
1009
+ return exists;
1010
+ }
840
1011
 
841
- async function verifyPermission(fileHandle, readWrite) {
842
- const options = {};
843
- if (readWrite) {
844
- options.mode = "readwrite";
845
- }
846
- let granted = false;
847
- // Check if permission was already granted. If so, return true.
848
- if ((await fileHandle.queryPermission(options)) === "granted") {
849
- granted = true;
850
- }
851
- // Request permission. If the user grants permission, return true.
852
- if (
853
- !granted && (await fileHandle.requestPermission(options)) === "granted"
854
- ) {
855
- granted = true;
856
- }
857
- // The user didn't grant permission, so return false.
858
- return granted;
1012
+ async function verifyPermission(fileHandle, readWrite) {
1013
+ const options = {};
1014
+ if (readWrite) {
1015
+ options.mode = "readwrite";
1016
+ }
1017
+ let granted = false;
1018
+ // Check if permission was already granted. If so, return true.
1019
+ if ((await fileHandle.queryPermission(options)) === "granted") {
1020
+ granted = true;
1021
+ }
1022
+ // Request permission. If the user grants permission, return true.
1023
+ if (
1024
+ !granted && (await fileHandle.requestPermission(options)) === "granted"
1025
+ ) {
1026
+ granted = true;
859
1027
  }
1028
+ // The user didn't grant permission, so return false.
1029
+ return granted;
860
1030
  }
861
1031
 
862
- export { BrowserFilesApi, FilesApi, MemFilesApi, NodeFilesApi, addFilesApiLogger, extensionToMimeType, getMimeType, mimeTypeToExtensions, mimeTypes, mimeTypesDescriptions, normalizePath, openBrowserFilesApi };
1032
+ export { BrowserFilesApi, FilesApi, MemFilesApi, NodeFilesApi, addFilesApiLogger, extensionToMimeType, getMimeType, isHandlerAccessible, mimeTypeToExtensions, mimeTypes, mimeTypesDescriptions, normalizePath, openBrowserFilesApi, verifyPermission };
package/dist/index.min.js CHANGED
@@ -1,2 +1,2 @@
1
- // @statewalker/webrun-files v0.1.0 https://github.com/statewalker/webrun-files Copyright 2023 Mikhail Kotelnikov
2
- function t(t){const e=t.split("/").filter((t=>!!t&&"."!==t));return 0===e.length&&e.push(""),e.unshift(""),e.join("/")}function e(){throw new Error("Not implemented.")}class i{constructor(t){this.options=t||{}}async*list(t,i={}){yield e()}async stats(t){return e()}async remove(t){return e()}async write(t,i){return e()}async*read(t,i){yield e()}async copy(t,e,i={}){const n=async(t,e,i)=>{e=`${e}${i.path.substring(t.length)}`;const n=this.read(i);return await this.write(e,n)},s=void 0===i.recursive||!!i.recursive,o=await this.stats(t);if("directory"===o.kind)for await(const o of this.list(t,{...i,recursive:s}))"directory"!==o.kind&&await n(t,e,o);else await n(t,e,o);return!0}async move(t,e,i={}){return!!await this.copy(t,e,i)&&(await this.remove(t),!0)}normalizePath(e){return t("object"==typeof e?e.path:e+"")}}const n=[{extensions:["aac"],mime:"audio/aac",description:"AAC audio"},{extensions:["abw"],mime:"application/x-abiword",description:"AbiWord document"},{extensions:["arc"],mime:"application/x-freearc",description:"Archive document (multiple files embedded)"},{extensions:["avi"],mime:"video/x-msvideo",description:"AVI: Audio Video Interleave"},{extensions:["azw"],mime:"application/vnd.amazon.ebook",description:"Amazon Kindle eBook format"},{extensions:["bin"],mime:"application/octet-stream",description:"Any kind of binary data"},{extensions:["bmp"],mime:"image/bmp",description:"Windows OS/2 Bitmap Graphics"},{extensions:["bz"],mime:"application/x-bzip",description:"BZip archive"},{extensions:["bz2"],mime:"application/x-bzip2",description:"BZip2 archive"},{extensions:["csh"],mime:"application/x-csh",description:"C-Shell script"},{extensions:["css"],mime:"text/css",description:"Cascading Style Sheets (CSS)"},{extensions:["csv"],mime:"text/csv",description:"Comma-separated values (CSV)"},{extensions:["doc"],mime:"application/msword",description:"Microsoft Word"},{extensions:["docx"],mime:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",description:"Microsoft Word (OpenXML)"},{extensions:["eot"],mime:"application/vnd.ms-fontobject",description:"MS Embedded OpenType fonts"},{extensions:["epub"],mime:"application/epub+zip",description:"Electronic publication (EPUB)"},{extensions:["gz"],mime:"application/gzip",description:"GZip Compressed Archive"},{extensions:["gif"],mime:"image/gif",description:"Graphics Interchange Format (GIF)"},{extensions:["htm","html"],mime:"text/html",description:"HyperText Markup Language (HTML)"},{extensions:["ico"],mime:"image/vnd.microsoft.icon",description:"Icon format"},{extensions:["ics"],mime:"text/calendar",description:"iCalendar format"},{extensions:["jar"],mime:"application/java-archive",description:"Java Archive (JAR)"},{extensions:["jpg","jpeg"],mime:"image/jpeg",description:"JPEG images"},{extensions:["js"],mime:"text/javascript",description:"JavaScript per the following specifications: https://html.spec.whatwg.org/multipage/#scriptingLanguages, https://html.spec.whatwg.org/multipage/#dependencies:willful-violation, https://datatracker.ietf.org/doc/draft-ietf-dispatch-javascript-mjs/"},{extensions:["json"],mime:"application/json",description:"JSON format"},{extensions:["jsonld"],mime:"application/ld+json",description:"JSON-LD format"},{extensions:["mid","midi"],mime:"application/ld+json",description:"Musical Instrument Digital Interface (MIDI)\taudio/midi audio/x-midi"},{extensions:["mjs"],mime:"text/javascript",description:"JavaScript module"},{extensions:["mp3"],mime:"audio/mpeg",description:"MP3 audio"},{extensions:["md"],mime:"text/markdown",description:"Markdown"},{extensions:["mpeg"],mime:"video/mpeg",description:"MPEG Video"},{extensions:["mpkg"],mime:"application/vnd.apple.installer+xml",description:"Apple Installer Package"},{extensions:["odp"],mime:"application/vnd.oasis.opendocument.presentation",description:"OpenDocument presentation document"},{extensions:["ods"],mime:"application/vnd.oasis.opendocument.spreadsheet",description:"OpenDocument spreadsheet document"},{extensions:["odt"],mime:"application/vnd.oasis.opendocument.text",description:"OpenDocument text document"},{extensions:["oga"],mime:"audio/ogg",description:"OGG audio"},{extensions:["ogv"],mime:"video/ogg",description:"OGG video"},{extensions:["ogx"],mime:"application/ogg",description:"OGG"},{extensions:["opus"],mime:"audio/opus",description:"Opus audio"},{extensions:["otf"],mime:"font/otf",description:"OpenType font"},{extensions:["png"],mime:"image/png",description:"Portable Network Graphics"},{extensions:["pdf"],mime:"application/pdf",description:"Adobe Portable Document Format (PDF)"},{extensions:["php"],mime:"application/x-httpd-php",description:"Hypertext Preprocessor (Personal Home Page)"},{extensions:["ppt"],mime:"application/vnd.ms-powerpoint",description:"Microsoft PowerPoint"},{extensions:["pptx"],mime:"application/vnd.openxmlformats-officedocument.presentationml.presentation",description:"Microsoft PowerPoint (OpenXML)"},{extensions:["rar"],mime:"application/vnd.rar",description:"RAR archive"},{extensions:["rtf"],mime:"application/rtf",description:"Rich Text Format (RTF)"},{extensions:["sh"],mime:"application/x-sh",description:"Bourne shell script"},{extensions:["svg"],mime:"image/svg+xml",description:"Scalable Vector Graphics (SVG)"},{extensions:["swf"],mime:"application/x-shockwave-flash",description:"Small web format (SWF) or Adobe Flash document"},{extensions:["tar"],mime:"application/x-tar",description:"Tape Archive (TAR)"},{extensions:["tif","tiff"],mime:"image/tiff",description:"Tagged Image File Format (TIFF)"},{extensions:["ttf"],mime:"font/ttf",description:"TrueType Font"},{extensions:["txt"],mime:"text/plain",description:"Text, (generally ASCII or ISO 8859-n)"},{extensions:["vsd"],mime:"application/vnd.visio",description:"Microsoft Visio"},{extensions:["wav"],mime:"audio/wav",description:"Waveform Audio Format"},{extensions:["weba"],mime:"audio/webm",description:"WEBM audio"},{extensions:["webm"],mime:"video/webm",description:"WEBM video"},{extensions:["webp"],mime:"image/webp",description:"WEBP image"},{extensions:["woff"],mime:"font/woff",description:"Web Open Font Format (WOFF)"},{extensions:["woff2"],mime:"font/woff2",description:"Web Open Font Format (WOFF)"},{extensions:["xhtml"],mime:"application/xhtml+xml",description:"XHTML"},{extensions:["xls"],mime:"application/vnd.ms-excel",description:"Microsoft Excel"},{extensions:["xlsx"],mime:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",description:"Microsoft Excel (OpenXML)"},{extensions:["xml"],mime:"application/xml",description:"XML; application/xml - if not readable from casual users (RFC 3023, section 3); text/xml - if readable from casual users (RFC 3023, section 3)"},{extensions:["xul"],mime:"application/vnd.mozilla.xul+xml",description:"XUL"},{extensions:["zip"],mime:"application/zip",description:"ZIP archive"},{extensions:["3gp"],mime:"video/3gpp",description:"3GPP audio/video container"},{extensions:["3g2"],mime:"video/3gpp2",description:"3GPP2 audio/video container; audio/3gpp if it doesn't contain video; audio/3gpp2 if it doesn't contain video"},{extensions:["7z"],mime:"application/x-7z-compressed",description:"7-zip archive"},{extensions:["jsx"],mime:"text/jsx",description:"JavaScript Syntax Extension / JavaScript XML format"},{extensions:["ts"],mime:"text/ts",description:"TypeScript format"},{extensions:["tsx"],mime:"text/tsx",description:"TypeScript Syntax Extension / TypeScript XML format"}],s=n.reduce(((t,e)=>(t[e.mime]=e.description,t)),{}),o=n.reduce(((t,e)=>{for(const i of e.extensions)t[i]=e.mime;return t}),{}),a=n.reduce(((t,e)=>(t[e.mime]=e.extensions,t)),{});function r(t){const e=t=t||"",i=(t=(t=t.split("?").shift()).split("/").pop()).split(".").pop();let n=o[i];return n||(n=e.match(/api\.observablehq\.com/)||e.match(/\.ojs$/)?"application/javascript-observable":"application/binary"),n}class c extends i{constructor(t){super(t),this.index={"/":{kind:"directory",path:"/",name:""}}}async*list(t,{recursive:e=!1}={}){await this._init();const i=this.normalizePath(t),n=this._getPaths();for(const t of n){if(0!==t.indexOf(i)||t===i)continue;const n=t.substring(i.length+1).split("/");(e||1===n.length)&&(yield this._getFileInfo(t))}}async stats(t){await this._init();const e=this.normalizePath(t);return this._getFileInfo(e)}async remove(t){await this._init();const e=await this.stats(t);if(!e)return!1;for await(const{path:t}of this.list(e,{recursive:!0}))"/"!==t&&delete this.index[t];delete this.index[e.path]}async write(t,e){await this._init(),await this._doWrite(t,e)}async*read(t){await this._init();const e=this.normalizePath(t),i=this.index[e];if(!i||"file"!==i.kind)return;const n=i.content;yield*n}async _init(){return this._initPromise=this._initPromise||Promise.resolve().then((()=>this._addEntries(this.options.files)))}async _doWrite(t,e){const i=this.normalizePath(t).split("/").filter((t=>!!t)),n=[""];let s;for(let t=0;t<i.length;t++){const e=i[t];n.push(e);const o=n.join("/");s=this.index[o]=this.index[o]||{path:o,name:e,kind:"directory"}}s.kind="file",s.content=[],s.type=r(s.path),s.size=0,s.lastModified=Date.now(),"function"==typeof e&&(e=e());for await(let t of e)t=new Uint8Array(t),s.content.push(t),s.size+=t.length}async _addEntries(t={}){for(let[e,i]of Object.entries(t))"string"==typeof i?i=[(new TextEncoder).encode(i)]:i instanceof Uint8Array&&(i=[i]),await this._doWrite(e,i)}_getPaths(){return this._getIndexPaths(this.index)}_getIndexPaths(t){return Object.keys(t).sort((function(t,e){return t>e?1:t<e?-1:0}))}_getFileInfo(t){const e=this.index[t];if(!e)return null;const i={...e};return delete i.content,i}}class p extends i{constructor(t){if(super(t),!this.fs)throw new Error("File System is not defined.");if(!this.rootDir)throw new Error("Root directory is not defined.")}get rootDirSegments(){return this._rootDirSegments||(this._rootDirSegments=this._toPathSegments(this.options.rootDir||"./")),this._rootDirSegments}get rootDir(){return this._rootDir||(this._rootDir=this.rootDirSegments.join("/")),this._rootDir}get fs(){return this.options.fs}async _init(){return this._initialized=this._initialized||(async()=>{const t=this.rootDir;return await l(this.fs,t)})()}async*list(t,{recursive:e=!1}={}){await this._init();const i=this._expandPath(t),n=await this._stats(i);if(n&&"directory"===n.kind)for await(let t of f(this.fs,i,e))yield this._getFileInfo(t.path,t.stat)}async stats(t){await this._init();const e=this._expandPath(t);return this._stats(e)}async remove(t){await this._init();const e=this._expandPath(t);return await h(this.fs,e)}async write(t,e){await this._init();const i=this._expandPath(t),n=d(i);let s;await this.fs.mkdir(n,{recursive:!0});try{s=await this.fs.open(i,"w"),"function"==typeof e&&(e=e()),await s.writeFile(e)}finally{s&&await s.close()}}async*read(t,{start:e=0,bufferSize:i=8192}={}){await this._init();const n=this._expandPath(t);let s;try{s=await this.fs.open(n,"r");const t=0;for(let n=e,o=0;;n+=o){const e=new Uint8ClampedArray(i),a=await s.read(e,t,i,n);if(o=a.bytesRead,0===o)break;yield o<i?a.buffer.slice(0,o):a.buffer}}finally{s&&await s.close()}}async _stats(t){try{const e=await this.fs.stat(t);return this._getFileInfo(t,e)}catch(t){if("ENOENT"!==t.code)throw t;return null}}_getFileInfo(t,e){const i=this._toPathSegments(t).slice(this.rootDirSegments.length);i.unshift("");const n=i[i.length-1];let s={path:t=i.join("/"),name:n,kind:""};return e.isFile()?(s.size=e.size||0,s.type=r(t),s.kind="file",s.lastModified=new Date(e.mtime).getTime()):s.kind="directory",s}_toPathSegments(t){return(t=this.normalizePath(t)).split("/")}_expandPath(t){const e="object"==typeof t?t.path:t+"",i=this._toPathSegments(e).filter((t=>!!t));return i.unshift(...this.rootDirSegments),i.join("/")}}function d(t){const e=t.split("/");return e.pop(),e.join("/")}function m(...t){let e=t.map((t=>t.split("/"))).reduce(((t,e)=>(t.push(...e),t)),[]);const i=e.shift();return e=e.filter((t=>!!t&&"."!==t)),e.unshift(i),e.join("/")}async function l(t,e){try{const i=await t.stat(e);if(i&&i.isDirectory())return!0}catch(t){}const i=d(e);return e!==i&&(await l(t,i),await t.mkdir(e,{recursive:!0}),!0)}async function h(t,e){try{if((await t.stat(e)).isDirectory()){for await(let i of f(t,e,!1))await h(t,i.path);await t.rmdir(e)}else await t.unlink(e)}catch(t){if("ENOENT"!==t.code)throw t}}async function*f(t,e,i){try{if((await t.stat(e)).isDirectory()){const n=await t.readdir(e);for(const s of n.sort()){const n=m(e,s);let o=await t.stat(n);yield{path:n,stat:o},i&&o.isDirectory()&&(yield*f(t,n,!0))}}}catch(t){if("ENOENT"!==t.code)throw t}}class u extends i{constructor(t){if(super(t),!this.rootHandler)throw new Error("Root directory handler is not defined")}get rootHandler(){return this.options.rootHandler}async*list(t,{recursive:e=!1}={}){const i=this._getPathSegments(t),n=await this._getHandler(i,!1),s=this;"directory"===n.kind&&(yield*async function*t(e,i,n){for await(const o of i.values())yield await s._getFileInfo([...e,o.name],o),n&&"directory"==o.kind&&(yield*t([...e,o.name],o,n))}(i,n,e))}async stats(t){const e=this._getPathSegments(t),i=await this._getHandler(e,!1);return i?await this._getFileInfo(e,i):null}async remove(t){let e=this._getPathSegments(t);const i=e.pop(),n=await this._getHandler(e,!1),s=[];if(i)s.push(i);else for await(const t of n.values())s.push(t.name);for(const t of s)await n.removeEntry(t,{recursive:!0});return!0}async write(t,e){const i=this._getPathSegments(t),n=await this._getFileHandler(i),s=await n.createWritable();try{"function"==typeof e&&(e=e());for await(let t of e)await s.write(t.buffer?t.buffer:t)}finally{await s.close()}}async*read(t){const e=this._getPathSegments(t),i=await this._getFileHandler(e,!1);if(!i)return;const n=(await i.getFile()).stream().getReader();try{let t;for(;(t=await n.read())&&!t.done;)yield t.value}finally{n.cancel&&n.cancel()}}async _getHandler(t,e=!1){let i;try{i=await this._getFileHandler(t,e)}catch(n){try{i=await this._getDirectoryHandler(t,e)}catch(t){}}return i}async _getDirectoryHandler(t,e=!0){t=[...t];let i=this.rootHandler;for(let n of t)i=await i.getDirectoryHandle(n,{create:e});return i}async _getFileHandler(t,e=!0){const i=(t=[...t]).pop();let n=this.rootHandler;for(let e of t)n=await n.getDirectoryHandle(e,{create:!0});return await n.getFileHandle(i,{create:e})}async _getFileInfo(t,e){const i=(t=[...t])[t.length-1];t.unshift("");const n=t.join("/"),s={kind:"",path:n,name:i};if("directory"===e.kind)s.kind="directory";else{s.kind="file";const t=await e.getFile();s.size=t.size,s.type=r(n),s.lastModified=t.lastModified}return s}_getPathSegments(t){return("object"==typeof t?t.path:t+"").split("/").filter((t=>!!t))}}function x({filesApi:t,log:e=console.log,wrapper:i={}}){return["list","stats","remove","write","read","copy","move"].reduce(((i,n)=>{const s=t[n].bind(t);return i[n]=(...i)=>{let o={method:n,stage:"enter",args:i};try{return e(o),o.result=s.call(t,...i)}catch(t){o.error=t}finally{o.stage="exit",e(o)}},i}),i)}async function w({handlerKey:t="root-dir",readwrite:e=!0,index:i={},get:n=(async t=>i[t]),set:s=(async(t,e)=>i[t]=e),del:o=(async t=>delete i[t])}={}){let a=await n(t);if(a||(a=await window.showDirectoryPicker(),await s(t,a)),!await async function(t,e){const i={};e&&(i.mode="readwrite");let n=!1;"granted"===await t.queryPermission(i)&&(n=!0);n||"granted"!==await t.requestPermission(i)||(n=!0);return n}(a,e))throw new Error("Access was not granted");if(!await async function(t){let e=!1;try{if("file"===t.kind)await t.getFile();else for await(let e of t.values())break;e=!0}catch(i){console.log(i,t),e=!1}return e}(a))throw await o(t),new Error("Can not access to the folder. Please try again.");return new u({rootHandler:a})}export{u as BrowserFilesApi,i as FilesApi,c as MemFilesApi,p as NodeFilesApi,x as addFilesApiLogger,o as extensionToMimeType,r as getMimeType,a as mimeTypeToExtensions,n as mimeTypes,s as mimeTypesDescriptions,t as normalizePath,w as openBrowserFilesApi};
1
+ // @statewalker/webrun-files v0.3.0 https://github.com/statewalker/webrun-files Copyright 2023 Mikhail Kotelnikov
2
+ function t(t){const e=t.split("/").filter((t=>!!t&&"."!==t));return 0===e.length&&e.push(""),e.unshift(""),e.join("/")}function e(){throw new Error("Not implemented.")}class i{constructor(t){this.options=t||{}}async*list(t,i={}){yield e()}async stats(t){return e()}async remove(t){return e()}async write(t,i){return e()}async*read(t,i){yield e()}async copy(t,e,i={}){const n=async(t,e,i)=>{e=`${e}${i.path.substring(t.length)}`;const n=this.read(i);return await this.write(e,n)},s=void 0===i.recursive||!!i.recursive,o=await this.stats(t);if("directory"===o.kind)for await(const o of this.list(t,{...i,recursive:s}))"directory"!==o.kind&&await n(t,e,o);else await n(t,e,o);return!0}async move(t,e,i={}){return!!await this.copy(t,e,i)&&(await this.remove(t),!0)}normalizePath(e){return t("object"==typeof e?e.path:e+"")}}const n=[{extensions:["aac"],mime:"audio/aac",description:"AAC audio"},{extensions:["abw"],mime:"application/x-abiword",description:"AbiWord document"},{extensions:["arc"],mime:"application/x-freearc",description:"Archive document (multiple files embedded)"},{extensions:["avi"],mime:"video/x-msvideo",description:"AVI: Audio Video Interleave"},{extensions:["azw"],mime:"application/vnd.amazon.ebook",description:"Amazon Kindle eBook format"},{extensions:["bin"],mime:"application/octet-stream",description:"Any kind of binary data"},{extensions:["bmp"],mime:"image/bmp",description:"Windows OS/2 Bitmap Graphics"},{extensions:["bz"],mime:"application/x-bzip",description:"BZip archive"},{extensions:["bz2"],mime:"application/x-bzip2",description:"BZip2 archive"},{extensions:["csh"],mime:"application/x-csh",description:"C-Shell script"},{extensions:["css"],mime:"text/css",description:"Cascading Style Sheets (CSS)"},{extensions:["csv"],mime:"text/csv",description:"Comma-separated values (CSV)"},{extensions:["doc"],mime:"application/msword",description:"Microsoft Word"},{extensions:["docx"],mime:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",description:"Microsoft Word (OpenXML)"},{extensions:["eot"],mime:"application/vnd.ms-fontobject",description:"MS Embedded OpenType fonts"},{extensions:["epub"],mime:"application/epub+zip",description:"Electronic publication (EPUB)"},{extensions:["gz"],mime:"application/gzip",description:"GZip Compressed Archive"},{extensions:["gif"],mime:"image/gif",description:"Graphics Interchange Format (GIF)"},{extensions:["htm","html"],mime:"text/html",description:"HyperText Markup Language (HTML)"},{extensions:["ico"],mime:"image/vnd.microsoft.icon",description:"Icon format"},{extensions:["ics"],mime:"text/calendar",description:"iCalendar format"},{extensions:["jar"],mime:"application/java-archive",description:"Java Archive (JAR)"},{extensions:["jpg","jpeg"],mime:"image/jpeg",description:"JPEG images"},{extensions:["js"],mime:"text/javascript",description:"JavaScript per the following specifications: https://html.spec.whatwg.org/multipage/#scriptingLanguages, https://html.spec.whatwg.org/multipage/#dependencies:willful-violation, https://datatracker.ietf.org/doc/draft-ietf-dispatch-javascript-mjs/"},{extensions:["json"],mime:"application/json",description:"JSON format"},{extensions:["jsonld"],mime:"application/ld+json",description:"JSON-LD format"},{extensions:["mid","midi"],mime:"application/ld+json",description:"Musical Instrument Digital Interface (MIDI)\taudio/midi audio/x-midi"},{extensions:["mjs"],mime:"text/javascript",description:"JavaScript module"},{extensions:["mp3"],mime:"audio/mpeg",description:"MP3 audio"},{extensions:["md"],mime:"text/markdown",description:"Markdown"},{extensions:["mpeg"],mime:"video/mpeg",description:"MPEG Video"},{extensions:["mpkg"],mime:"application/vnd.apple.installer+xml",description:"Apple Installer Package"},{extensions:["odp"],mime:"application/vnd.oasis.opendocument.presentation",description:"OpenDocument presentation document"},{extensions:["ods"],mime:"application/vnd.oasis.opendocument.spreadsheet",description:"OpenDocument spreadsheet document"},{extensions:["odt"],mime:"application/vnd.oasis.opendocument.text",description:"OpenDocument text document"},{extensions:["oga"],mime:"audio/ogg",description:"OGG audio"},{extensions:["ogv"],mime:"video/ogg",description:"OGG video"},{extensions:["ogx"],mime:"application/ogg",description:"OGG"},{extensions:["opus"],mime:"audio/opus",description:"Opus audio"},{extensions:["otf"],mime:"font/otf",description:"OpenType font"},{extensions:["png"],mime:"image/png",description:"Portable Network Graphics"},{extensions:["pdf"],mime:"application/pdf",description:"Adobe Portable Document Format (PDF)"},{extensions:["php"],mime:"application/x-httpd-php",description:"Hypertext Preprocessor (Personal Home Page)"},{extensions:["ppt"],mime:"application/vnd.ms-powerpoint",description:"Microsoft PowerPoint"},{extensions:["pptx"],mime:"application/vnd.openxmlformats-officedocument.presentationml.presentation",description:"Microsoft PowerPoint (OpenXML)"},{extensions:["rar"],mime:"application/vnd.rar",description:"RAR archive"},{extensions:["rtf"],mime:"application/rtf",description:"Rich Text Format (RTF)"},{extensions:["sh"],mime:"application/x-sh",description:"Bourne shell script"},{extensions:["svg"],mime:"image/svg+xml",description:"Scalable Vector Graphics (SVG)"},{extensions:["swf"],mime:"application/x-shockwave-flash",description:"Small web format (SWF) or Adobe Flash document"},{extensions:["tar"],mime:"application/x-tar",description:"Tape Archive (TAR)"},{extensions:["tif","tiff"],mime:"image/tiff",description:"Tagged Image File Format (TIFF)"},{extensions:["ttf"],mime:"font/ttf",description:"TrueType Font"},{extensions:["txt"],mime:"text/plain",description:"Text, (generally ASCII or ISO 8859-n)"},{extensions:["vsd"],mime:"application/vnd.visio",description:"Microsoft Visio"},{extensions:["wav"],mime:"audio/wav",description:"Waveform Audio Format"},{extensions:["weba"],mime:"audio/webm",description:"WEBM audio"},{extensions:["webm"],mime:"video/webm",description:"WEBM video"},{extensions:["webp"],mime:"image/webp",description:"WEBP image"},{extensions:["woff"],mime:"font/woff",description:"Web Open Font Format (WOFF)"},{extensions:["woff2"],mime:"font/woff2",description:"Web Open Font Format (WOFF)"},{extensions:["xhtml"],mime:"application/xhtml+xml",description:"XHTML"},{extensions:["xls"],mime:"application/vnd.ms-excel",description:"Microsoft Excel"},{extensions:["xlsx"],mime:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",description:"Microsoft Excel (OpenXML)"},{extensions:["xml"],mime:"application/xml",description:"XML; application/xml - if not readable from casual users (RFC 3023, section 3); text/xml - if readable from casual users (RFC 3023, section 3)"},{extensions:["xul"],mime:"application/vnd.mozilla.xul+xml",description:"XUL"},{extensions:["zip"],mime:"application/zip",description:"ZIP archive"},{extensions:["3gp"],mime:"video/3gpp",description:"3GPP audio/video container"},{extensions:["3g2"],mime:"video/3gpp2",description:"3GPP2 audio/video container; audio/3gpp if it doesn't contain video; audio/3gpp2 if it doesn't contain video"},{extensions:["7z"],mime:"application/x-7z-compressed",description:"7-zip archive"},{extensions:["jsx"],mime:"text/jsx",description:"JavaScript Syntax Extension / JavaScript XML format"},{extensions:["ts"],mime:"text/ts",description:"TypeScript format"},{extensions:["tsx"],mime:"text/tsx",description:"TypeScript Syntax Extension / TypeScript XML format"}],s=n.reduce(((t,e)=>(t[e.mime]=e.description,t)),{}),o=n.reduce(((t,e)=>{for(const i of e.extensions)t[i]=e.mime;return t}),{}),a=n.reduce(((t,e)=>(t[e.mime]=e.extensions,t)),{});function r(t){const e=t=t||"",i=(t=(t=t.split("?").shift()).split("/").pop()).split(".").pop();let n=o[i];return n||(n=e.match(/api\.observablehq\.com/)||e.match(/\.ojs$/)?"application/javascript-observable":"application/binary"),n}class c extends i{constructor(t){super(t),this.index={"/":{kind:"directory",path:"/",name:""}}}async*list(t,{recursive:e=!1}={}){await this._init();const i=this.normalizePath(t),n=this._getPaths();for(const t of n){if(0!==t.indexOf(i)||t===i)continue;const n=t.substring(i.length+1).split("/");(e||1===n.length)&&(yield this._getFileInfo(t))}}async stats(t){await this._init();const e=this.normalizePath(t);return this._getFileInfo(e)}async remove(t){await this._init();const e=await this.stats(t);if(!e)return!1;for await(const{path:t}of this.list(e,{recursive:!0}))"/"!==t&&delete this.index[t];delete this.index[e.path]}async write(t,e){await this._init(),await this._doWrite(t,e)}async*read(t){await this._init();const e=this.normalizePath(t),i=this.index[e];if(!i||"file"!==i.kind)return;const n=i.content;yield*n}async _init(){return this._initPromise=this._initPromise||Promise.resolve().then((()=>this._addEntries(this.options.files)))}async _doWrite(t,e){const i=this.normalizePath(t).split("/").filter((t=>!!t)),n=[""];let s;for(let t=0;t<i.length;t++){const e=i[t];n.push(e);const o=n.join("/");s=this.index[o]=this.index[o]||{path:o,name:e,kind:"directory"}}s.kind="file",s.content=[],s.type=r(s.path),s.size=0,s.lastModified=Date.now(),"function"==typeof e&&(e=e());for await(let t of e)t=new Uint8Array(t),s.content.push(t),s.size+=t.length}async _addEntries(t={}){for(let[e,i]of Object.entries(t))"string"==typeof i?i=[(new TextEncoder).encode(i)]:i instanceof Uint8Array&&(i=[i]),await this._doWrite(e,i)}_getPaths(){return this._getIndexPaths(this.index)}_getIndexPaths(t){return Object.keys(t).sort((function(t,e){return t>e?1:t<e?-1:0}))}_getFileInfo(t){const e=this.index[t];if(!e)return null;const i={...e};return delete i.content,i}}class d extends i{constructor(t){if(super(t),!this.fs)throw new Error("File System is not defined.");if(!this.rootDir)throw new Error("Root directory is not defined.")}get rootDirSegments(){return this._rootDirSegments||(this._rootDirSegments=this._toPathSegments(this.options.rootDir||"./")),this._rootDirSegments}get rootDir(){return this._rootDir||(this._rootDir=this.rootDirSegments.join("/")),this._rootDir}get fs(){return this.options.fs}async _init(){return this._initialized=this._initialized||(async()=>{const t=this.rootDir;return await m(this.fs,t)})()}async*list(t,{recursive:e=!1}={}){await this._init();const i=this._expandPath(t),n=await this._stats(i);if(n&&"directory"===n.kind)for await(let t of f(this.fs,i,e))yield this._getFileInfo(t.path,t.stat)}async stats(t){await this._init();const e=this._expandPath(t);return this._stats(e)}async remove(t){await this._init();const e=this._expandPath(t);return await h(this.fs,e)}async write(t,e){await this._init();const i=this._expandPath(t),n=p(i);let s;await this.fs.mkdir(n,{recursive:!0});try{s=await this.fs.open(i,"w"),"function"==typeof e&&(e=e()),await s.writeFile(e)}finally{s&&await s.close()}}async*read(t,{start:e=0,bufferSize:i=8192}={}){await this._init();const n=this._expandPath(t);let s;try{s=await this.fs.open(n,"r");const t=0;for(let n=e,o=0;;n+=o){const e=new Uint8ClampedArray(i),a=await s.read(e,t,i,n);if(o=a.bytesRead,0===o)break;yield o<i?a.buffer.slice(0,o):a.buffer}}finally{s&&await s.close()}}async _stats(t){try{const e=await this.fs.stat(t);return this._getFileInfo(t,e)}catch(t){if("ENOENT"!==t.code)throw t;return null}}_getFileInfo(t,e){const i=this._toPathSegments(t).slice(this.rootDirSegments.length);i.unshift("");const n=i[i.length-1];let s={path:t=i.join("/"),name:n,kind:""};return e.isFile()?(s.size=e.size||0,s.type=r(t),s.kind="file",s.lastModified=new Date(e.mtime).getTime()):s.kind="directory",s}_toPathSegments(t){return(t=this.normalizePath(t)).split("/")}_expandPath(t){const e="object"==typeof t?t.path:t+"",i=this._toPathSegments(e).filter((t=>!!t));return i.unshift(...this.rootDirSegments),i.join("/")}}function p(t){const e=t.split("/");return e.pop(),e.join("/")}function l(...t){let e=t.map((t=>t.split("/"))).reduce(((t,e)=>(t.push(...e),t)),[]);const i=e.shift();return e=e.filter((t=>!!t&&"."!==t)),e.unshift(i),e.join("/")}async function m(t,e){try{const i=await t.stat(e);if(i&&i.isDirectory())return!0}catch(t){}const i=p(e);return e!==i&&(await m(t,i),await t.mkdir(e,{recursive:!0}),!0)}async function h(t,e){try{if((await t.stat(e)).isDirectory()){for await(let i of f(t,e,!1))await h(t,i.path);await t.rmdir(e)}else await t.unlink(e)}catch(t){if("ENOENT"!==t.code)throw t}}async function*f(t,e,i){try{if((await t.stat(e)).isDirectory()){const n=await t.readdir(e);for(const s of n.sort()){const n=l(e,s);let o=await t.stat(n);yield{path:n,stat:o},i&&o.isDirectory()&&(yield*f(t,n,!0))}}}catch(t){if("ENOENT"!==t.code)throw t}}class u{static canApply(t){return(t?.isDirectory||t?.isFile)&&"string"==typeof t?.name}async*listDirectoryChildren(t){let e=t.createReader();for(;;){const t=await new Promise(((t,i)=>e.readEntries(t,i)));if(!t.length)break;yield*t}}isDirectoryHandle(t){return t?.isDirectory}isFileHandle(t){return t?.isFile}async getSubdirectoryHandle(t,e,i){return new Promise(((n,s)=>t.getDirectory(e,{create:i},n,s)))}async getFileHandle(t,e,i=!0){return new Promise(((n,s)=>t.getFile(e,{create:i},n,s)))}async removeDirectoryEntry(t,e){return new Promise(((t,i)=>e.remove(t,i)))}async getFile(t){return await new Promise(((e,i)=>t.file(e,i)))}async getFileWriteStream(t){throw new Error("Not implemented")}}class y{static canApply(t){return"file"===t?.kind||"directory"===t?.kind}async*listDirectoryChildren(t){yield*t.values()}isDirectoryHandle(t){return"directory"===t?.kind}isFileHandle(t){return"file"===t?.kind}async getSubdirectoryHandle(t,e,i){return await t.getDirectoryHandle(e,{create:i})}async getFileHandle(t,e,i=!0){return await t.getFileHandle(e,{create:i})}async removeDirectoryEntry(t,e){return await t.removeEntry(e.name,{recursive:!0})}async getFile(t){return await t.getFile()}async getFileWriteStream(t){return await t.createWritable()}}class w extends i{constructor(t){if(super(t),!this.rootHandle)throw new Error("Root directory handle is not defined");if(!this.adapter)throw new Error("Can not define a browser adapter for the specified root handle")}get rootHandle(){return this.options.rootHandle||this.options.rootHandler}get adapter(){if(!this._adapter){const t=this.rootHandle;let e=u.canApply(t)?new u:y.canApply(t)?new y:null;this._adapter=e}return this._adapter}async*list(t,{recursive:e=!1}={}){const i=this._getPathSegments(t),n=await this._getHandle(i);if(!n)return;const s=this;this.adapter.isDirectoryHandle(n)&&(yield*async function*t(e,i,n){for await(const o of s._listDirectoryContent(i))yield await s._getFileInfo([...e,o.name],o),n&&s.adapter.isDirectoryHandle(o)&&(yield*t([...e,o.name],o,n))}(i,n,e))}async stats(t){const e=this._getPathSegments(t),i=await this._getHandle(e);return i?await this._getFileInfo(e,i):null}async remove(t){const e=async(t,i)=>{let n=!0;if(this.adapter.isDirectoryHandle(i)){const t=[];for await(let e of this.adapter.listDirectoryChildren(i))t.push(e);for(let s of t)n=n&&await e(i,s)}return t&&(n&=await this.adapter.removeDirectoryEntry(t,i)),n},i=this._getPathSegments(t),n=await this._getHandle(i);i.pop();const s=await this._getHandle(i);return e(s!==n?s:null,n)}async write(t,e){const i=this._getPathSegments(t),n=i.pop(),s=await this._getDirectoryHandle(i,!0),o=await this.adapter.getFileHandle(s,n,!0),a=await this.adapter.getFileWriteStream(o);try{"function"==typeof e&&(e=e());for await(let t of e)await a.write(t.buffer?t.buffer:t)}finally{await a.close()}}async*read(t){const e=this._getPathSegments(t),i=e.pop(),n=await this._getDirectoryHandle(e,!1),s=await this.adapter.getFileHandle(n,i,!1);if(!s)return;const o=(await this.adapter.getFile(s)).stream().getReader();try{let t;for(;(t=await o.read())&&!t.done;)yield await t.value}finally{await o.cancel&&o.cancel()}}async _getFileInfo(t,e){const i=(t=[...t])[t.length-1];t.unshift("");const n=t.join("/"),s={kind:"",path:n,name:i};if(this.adapter.isDirectoryHandle(e))s.kind="directory";else{s.kind="file";const t=await this.adapter.getFile(e);s.size=t.size,s.type=r(n),s.lastModified=t.lastModified}return s}_getPathSegments(t){return("object"==typeof t?t.path:t+"").split("/").filter((t=>!!t))}async _getHandle(t){const e=[...t],i=e.pop(),n=!1;try{const t=await this._getDirectoryHandle(e,n);try{return i?await this.adapter.getSubdirectoryHandle(t,i,n):t}catch(e){return await this.adapter.getFileHandle(t,i,n)}}catch(t){return}}async _getDirectoryHandle(t,e=!0){t=[...t];let i=this.rootHandle;for(let n of t)if(i=await this.adapter.getSubdirectoryHandle(i,n,e),!i)break;return i}async*_listDirectoryContent(t){let e=[];for await(const i of this.adapter.listDirectoryChildren(t))e.push(i);e=e.sort(((t,e)=>e.name>t.name?1:-1)),yield*e}}function g({filesApi:t,log:e=console.log,wrapper:i={}}){return["list","stats","remove","write","read","copy","move"].reduce(((i,n)=>{const s=t[n].bind(t);return i[n]=(...i)=>{let o={method:n,stage:"enter",args:i};try{return e(o),o.result=s.call(t,...i)}catch(t){o.error=t}finally{o.stage="exit",e(o)}},i}),i)}async function x({handlerKey:t="root-dir",readwrite:e=!0,index:i={},get:n=(async t=>i[t]),set:s=(async(t,e)=>i[t]=e),del:o=(async t=>delete i[t])}={}){let a=await n(t);if(a||(a=await window.showDirectoryPicker(),await s(t,a)),!await _(a,e))throw new Error("Access was not granted");if(!await v(a))throw await o(t),new Error("Can not access to the folder. Please try again.");return new w({rootHandler:a})}async function v(t){let e=!1;try{if("file"===t.kind)await t.getFile();else for await(let e of t.values())break;e=!0}catch(i){console.log(i,t),e=!1}return e}async function _(t,e){const i={};e&&(i.mode="readwrite");let n=!1;return"granted"===await t.queryPermission(i)&&(n=!0),n||"granted"!==await t.requestPermission(i)||(n=!0),n}export{w as BrowserFilesApi,i as FilesApi,c as MemFilesApi,d as NodeFilesApi,g as addFilesApiLogger,o as extensionToMimeType,r as getMimeType,v as isHandlerAccessible,a as mimeTypeToExtensions,n as mimeTypes,s as mimeTypesDescriptions,t as normalizePath,x as openBrowserFilesApi,_ as verifyPermission};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statewalker/webrun-files",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Basic files API",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/statewalker/webrun-files",
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "devDependencies": {
28
28
  "@statewalker/rollup": "^0.1.6",
29
- "@statewalker/webrun-files-tests": "https://github.com/statewalker/webrun-files-test",
29
+ "@statewalker/webrun-files-tests": "^0.1.0",
30
30
  "eslint": "^8",
31
31
  "expect.js": "^0.3",
32
32
  "mocha": "^10",