├── .editorconfig ├── .envrc ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .gitmodules ├── .npmrc ├── .prettierignore ├── LICENSE.md ├── README.md ├── esbuild.config.mjs ├── eslint.config.mjs ├── flake.lock ├── flake.nix ├── manifest.json ├── package.json ├── pnpm-lock.yaml ├── src ├── FSCache.ts ├── OriginalMetadataCache.ts ├── main.ts └── settings.ts ├── tsconfig.json └── version-bump.mjs /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 4 10 | tab_width = 4 11 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | on: 3 | push: 4 | tags: 5 | - "*" 6 | permissions: 7 | contents: write 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout Repository (with Submodules) 13 | uses: actions/checkout@v2 14 | with: 15 | submodules: recursive 16 | fetch-depth: 0 17 | - name: Install pnpm 18 | uses: pnpm/action-setup@v4 19 | with: 20 | version: 8 21 | - name: Use Node.js 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: "18.x" 25 | cache: "pnpm" 26 | 27 | - name: Install dependencies 28 | run: pnpm install --frozen-lockfile 29 | 30 | - name: Build 31 | id: build 32 | run: pnpm run build 33 | 34 | - name: Copy manifest and styles to dist 35 | run: cp manifest.json dist/ 36 | 37 | - name: Release 38 | uses: softprops/action-gh-release@v1 39 | if: startsWith(github.ref, 'refs/tags/') 40 | with: 41 | name: ${{ github.ref_name }} 42 | tag_name: ${{ github.ref }} 43 | files: | 44 | dist/main.js 45 | dist/manifest.json 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | .home 4 | 5 | # Intellij 6 | *.iml 7 | .idea 8 | 9 | main.js 10 | dist 11 | 12 | # npm 13 | node_modules 14 | node_modules.bak 15 | .direnv 16 | test 17 | 18 | # Don't include the compiled main.js file in the repo. 19 | # They should be uploaded to GitHub releases instead. 20 | main.js 21 | 22 | # Exclude sourcemaps 23 | *.map 24 | 25 | # obsidian 26 | data.json 27 | 28 | # Exclude macOS Finder (System Explorer) View States 29 | .DS_Store 30 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "obsidian-typings"] 2 | path = obsidian-typings 3 | url = git@github.com:d7sd6u/obsidian-typings.git 4 | [submodule "obsidian-reusables"] 5 | path = obsidian-reusables 6 | url = git@github.com:d7sd6u/obsidian-reusables.git 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | obsidian-typings 2 | node_modules 3 | .direnv 4 | dist 5 | pnpm-lock.yaml 6 | flake.lock -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ~~Lazy~~ Cached Vault Loader 2 | 3 | Do you have a big vault (3k+ files)? Have you tried to use mobile Obsidian to open it? Then you know how awfully long the loading takes. At some point, the mobile app simply becomes completely unusable, not just for quick notes. The Obsidian team has claimed that they have fixed that in 1.7, but this is just plain false, as the app is still unbearable for anything larger than a few thousand files. 4 | 5 | Now, behold! With this plugin, that experience is a thing of the past! Be ready for under 2-3 seconds (sic!) of loading time for even the largest of vaults (30k and up)! 6 | 7 | https://github.com/user-attachments/assets/418e8a3a-1d52-49e6-876f-3134a103355c 8 | 9 | ## How it works 10 | 11 | On the first load, the app loads normally while the plugin populates the cache in IndexedDB. On subsequent loads, this plugin uses the cache to boot all the underlying machinery that Obsidian uses and traverses the file system in the background to reconcile possible changes. Obviously, the plugin also keeps the cache in sync with the file system during the whole session, so the experience should be absolutely the same as without the plugin unless you heavily mess with your vault outside the app between app sessions (in such a case, at each app startup, these changes would be propagated within the ordinary "vanilla" loading time frame). 12 | 13 | ## Why do you have such a large vault? 14 | 15 | I do not agree with the author of Trillium on the storage medium for notes, but I do agree that one's personal storage medium has to be scalable up to one's lifespan. So, with 10+ notes a day for 70 years, that's around hundreds of thousands of notes. And that's not that much! Our computers are insanely fast and are capable of storing thousands of times more than that. It has to be easy-peasy! But unfortunately, more often than not, this is not the case. 16 | 17 | I also have to make one remark. To be frank, my 30k vault is only 5% notes, and the rest are files. I consider my personal knowledge base to be a personal data base, so all my photos, bookmarks, archives, books, documents, music, videos, playlists, and everything else that is meaningful and should be persisted is stored there. As a result, all my data is interconnected. I can tag, reference, list, embed, search, and filter all of my data, no matter its file type or origin. 18 | 19 | ## I would like to follow your suit! Do you have other awesome plugins that would help me do this? 20 | 21 | Yeah! I have written a bunch of other plugins with that goal in mind: 22 | 23 | - [auto-folder-note-paste](https://github.com/d7sd6u/obsidian-auto-folder-note-paste) - makes sure your attachments are "inside" your note on paste and drag'n'drop by making your note a folder note, so from this: 24 | 25 | ``` 26 | . 27 | └── Note.md 28 | ``` 29 | 30 | To this: 31 | 32 | ``` 33 | . 34 | └── Note 35 | ├── attachment.png 36 | └── Note.md 37 | ``` 38 | 39 | - [folders-graph](https://github.com/d7sd6u/obsidian-folders-graph) - adds folders as nodes to graph views 40 | - [reveal-folded](https://github.com/d7sd6u/obsidian-reveal-folded) - reveals current file in file explorer while collapsing everything else 41 | - [hide-index-files](https://github.com/d7sd6u/obsidian-hide-index-files) - hides folder notes (index files) from file explorer. Use with [folder-notes](https://github.com/LostPaul/obsidian-folder-notes) (it has the same feature, but that feature is poorly implemented and I am too lazy to submit a patch to this wonderful plugin) so that your tree looks like this: 42 | 43 | ``` 44 | . 45 | └── Note 46 | └── attachment.png 47 | ``` 48 | 49 | And not like this: 50 | 51 | ``` 52 | . 53 | └── Note 54 | ├── attachment.png 55 | └── Note.md 56 | ``` 57 | 58 | - [crosslink-advanced](https://github.com/d7sd6u/obsidian-crosslink-advanced) - adds commands to deal with ftags-oriented vaults: add ftags, create child note, open random unftagged file, etc. 59 | - [virtual-dirs](https://github.com/d7sd6u/obsidian-virtual-dirs) - adds "virtual" folder files / folder indexes. You can open them, you can search for them, but they do not take space and "materialize" whenever you want a _real_ folder note 60 | - [viewer-ftags](https://github.com/d7sd6u/obsidian-viewer-ftags) - adds ftags as chips on top of markdown/file editors/previews. And children as differently styled chips too! 61 | - [git-annex-autofetch](https://github.com/d7sd6u/obsidian-git-annex-autofetch) - lets you open annexed but not present files as if they were right on your device (basically, NFS/overlay-fs hybrid in your Obsidian) 62 | 63 | First, folder/index notes. Basically, that's just a way to remove the distinction between a folder and a note, just like in Notion or Trillium. There is no reason why you should not be able to use a folder as a note, as it is often the case that there is some data that relates to every child in a folder. Maybe that's a description of a series of lectures or a commentary on a book (which is basically a folder for pages). Even attachments benefit immensely from removing this distinction, as rather than cramming every attachment from every note in your vault into a single auxiliary directory (where they are disconnected from their related notes and are completely unrelated to each other) or making them a sibling file (which is very easy to lose while moving notes and pollutes the note's parent folder), all attachments are just children of their respective notes. 64 | 65 | As Obsidian stores notes as plain files, this pattern is usually implemented as folder/index notes. For example, like this: 66 | 67 | ``` 68 | . 69 | └── Note 70 | ├── attachment.png 71 | └── Note.md # I prefer (and support) this one! Although ugly, it has the best compatibility during search and moving with external tools 72 | ``` 73 | 74 | This: 75 | 76 | ``` 77 | . 78 | └── Note 79 | ├── attachment.png 80 | └── index.md # I wish it was supported everywhere. It is robust to "name-drift" during renaming and is arguably best-looking, but search in text editors usually sucks with these 81 | ``` 82 | 83 | Or this: 84 | 85 | ``` 86 | . 87 | ├── Note 88 | │ └── attachment.png 89 | └── Note.md 90 | ``` 91 | 92 | Second, ftags. Basically, an ftag is a hybrid of a tag and a folder. You may call it a hierarchical tag (a tag that can have tags) or a taggable folder (a folder with more than one parent folder). In Trillium, that concept is called "Cloning"; in Linux, symlinks essentially are ftags in disguise. 93 | 94 | Why are they useful? It is easier to understand by following an example: 95 | You store one note per concept. You have "Paris," "Eiffel Tower," "Attractions," "France," and "Capitals." With folders, you can start sorting everything like this: Capitals -> Paris -> Eiffel Tower. So far, so good. But why not Attractions -> Eiffel Tower? Why not France -> Paris -> Eiffel Tower? Because you cannot have a file in two folders at once, so you have to choose either one or another. This is not good; your only choice is to lose valuable information (that the Eiffel Tower is an attraction). Other "solutions" include duplicating folders (creating an Attractions folder for each city), which makes searching harder and essentially loses information, just in a more peculiar way. 96 | 97 | ```mermaid 98 | graph TD 99 | Paris["Paris/"] --> Eiffel_Tower["Eiffel_Tower"] 100 | Capitals["Capitals/"] --> Paris 101 | Attractions["Attractions"] -.-x Eiffel_Tower 102 | France["France"] -.-x Paris 103 | 104 | linkStyle 2 stroke:#D50000 105 | linkStyle 3 stroke:#D50000,fill:none 106 | ``` 107 | 108 | Now let's try using tags. We could go like this: #Attractions, #Paris, #France -> Eiffel Tower. Have you spotted the problem? Now we have redundancy! This is also bad, as it is obvious that something in #Paris is also in #France, and we have to either add both by hand (and risk hesitation and/or human error) or lose information. 109 | 110 | ```mermaid 111 | graph TD 112 | Paris[#Paris] --> Eiffel_Tower 113 | Attractions[#Attractions] --> Eiffel_Tower 114 | Capitals["#Capitals (forgot to link!)"] 115 | Eiffel_Tower 116 | France[#France] --> Eiffel_Tower 117 | France -.-x Paris 118 | 119 | linkStyle 3 stroke:#D50000 120 | ``` 121 | 122 | And no, nested tags are just duplicated tags in disguise. And see what problem we would have with them: #France/Paris -> Eiffel Tower or #Capitals/Paris -> Eiffel Tower? If only we could give #Paris several tags... 123 | 124 | ```mermaid 125 | graph TD 126 | Paris["Paris (#France/Paris)"] --> Eiffel_Tower 127 | Attractions[#Attractions] --> Eiffel_Tower 128 | Capitals["Capitals (#Capitals/Paris)"] 129 | Eiffel_Tower 130 | France["France (#France/Paris)"] --> Paris 131 | Capitals -.-x Paris 132 | 133 | linkStyle 3 stroke:#D50000 134 | ``` 135 | 136 | That's where ftags save the day! Now you can have an arbitrary DAG as your tag system. #Attractions, #Paris -> Eiffel Tower; #France, #Capitals -> #Paris 137 | 138 | ```mermaid 139 | graph TD 140 | Capitals --> Paris 141 | France --> Paris 142 | Paris --> Eiffel_Tower 143 | Attractions --> Eiffel_Tower 144 | ``` 145 | 146 | I model them with ordinary folders (supercharged with folder notes) and `.symlinks` frontmatter property that is filled with wikilinks to all child notes/files. This also means that every note is a tag and every tag is a note. 147 | Previously, I modeled it with symlinks (which is neat and tidy), but, though a part of the POSIX standard, a lot of software behaves poorly with symlinks. And it is totally not transportable to other OSes, even Android. 148 | 149 | Third, "annexed." You really should read about [git-annex](https://git-annex.branchable.com/), but TL;DR: store metadata about your files in Git, while their content may be stored and managed separately. It is like `git-lfs` but much, much more powerful and allows you to store large files only on selected devices/remotes rather than forcing you to store everything everywhere! So, for example, you can be sure you have at least two copies of the contents of each file, at least one copy on an offsite cold backup disk, and only _.mp3 files should be stored on your mobile phone. This way, you have at least two backups of every file, an offsite cold backup of every file, and your phone's storage is not overwhelmed with your _.raw image files collection. 150 | 151 | ## Disclaimer / Safety 152 | 153 | This plugin does not call any of the destructive file system APIs (write, move, delete), but as it patches very delicate, undocumented internal modules of Obsidian, beware that "Here be dragons." Always [backup](https://en.wikipedia.org/wiki/Backup#:~:text=3-2-1%20rule) your vault, whether you use this plugin or not. 154 | 155 | This plugin should not interfere with other plugins and should work transparently for both the user and other plugins, but as any plugin may monkey-patch anything anywhere arbitrarily, something may break somewhere. Keep that in mind and see the previous paragraph. 156 | 157 | ## Settings 158 | 159 | There is a button to clear the cache on the plugin's settings page. In the unlikely event of cache corruption, you can tap it and restart the app to repopulate the cache from scratch. 160 | 161 | ## Future features / TODO 162 | 163 | There should be a setting to turn on progressive loading during the app startup. Initially, this plugin was intended to be used without a cache, and the user could view how new files were added to the app's interface. As loading the cache is CPU-bound, progressive loading does not make sense at that stage, but during the initial population of the cache, that might help eliminate annoying wait times. 164 | 165 | Speaking of progressive vault loading, there should be an option to turn the cache off. Perhaps having non-existent files in search and file explorer for 10 to 20 seconds is annoying for someone. This is low-hanging fruit, though, so there is no reason not to implement that. 166 | 167 | There is a word "lazy" in the title for a reason. While there is no "laziness" in the vault loading right now, except for the deferral of vault traversing, in the future, opening (or even seeing) folders and vaults should trigger the loading/reconciliation of these paths. That would increase the reliability of the search and even open the possibility of full laziness—only loading opened paths and deferring everything else, with external tools updating search/backlinks/Dataview® cache. 168 | 169 | ## Contributing 170 | 171 | Issues and patches are welcome. This plugin is intended to be used with other plugins, and I will try to do my best to support this use case, but I retain the right to refuse to support any given plugin for arbitrary reasons. 172 | 173 | ## Support 174 | 175 | Thanks to the lack of available source code writing ambitious low-level reengineering plugins for Obsidian is hard and time-consuming. If you enjoy this plugin, you can [buy me a coffee](https://ko-fi.com/d7sd6u) (or better a coffee-subscription) and ensure that I can continue writing and supporting my (and others') plugins. 176 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from "builtin-modules"; 4 | 5 | const banner = `/* 6 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 7 | if you want to view the source, please visit the github repository of this plugin 8 | */ 9 | `; 10 | 11 | const prod = process.argv[2] === "production"; 12 | 13 | const context = await esbuild.context({ 14 | banner: { 15 | js: banner, 16 | }, 17 | entryPoints: ["src/main.ts"], 18 | bundle: true, 19 | external: [ 20 | "obsidian", 21 | "electron", 22 | "@codemirror/autocomplete", 23 | "@codemirror/collab", 24 | "@codemirror/commands", 25 | "@codemirror/language", 26 | "@codemirror/lint", 27 | "@codemirror/search", 28 | "@codemirror/state", 29 | "@codemirror/view", 30 | "@lezer/common", 31 | "@lezer/highlight", 32 | "@lezer/lr", 33 | ...builtins, 34 | ], 35 | format: "cjs", 36 | target: "es2018", 37 | logLevel: "info", 38 | sourcemap: prod ? false : "inline", 39 | treeShaking: true, 40 | outfile: "dist/main.js", 41 | }); 42 | 43 | if (prod) { 44 | await context.rebuild(); 45 | process.exit(0); 46 | } else { 47 | await context.watch(); 48 | } 49 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslint from "@eslint/js"; 2 | import tseslint from "typescript-eslint"; 3 | 4 | export default tseslint.config( 5 | eslint.configs.recommended, 6 | tseslint.configs.strictTypeChecked, 7 | tseslint.configs.stylisticTypeChecked, 8 | { 9 | rules: { 10 | "@typescript-eslint/restrict-template-expressions": [ 11 | "error", 12 | { allowNumber: true }, 13 | ], 14 | }, 15 | languageOptions: { 16 | parserOptions: { 17 | projectService: true, 18 | tsconfigRootDir: import.meta.dirname, 19 | }, 20 | }, 21 | }, 22 | ); 23 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1713564160, 6 | "narHash": "sha256-YguPZpiejgzLEcO36/SZULjJQ55iWcjAmf3lYiyV1Fo=", 7 | "owner": "nixos", 8 | "repo": "nixpkgs", 9 | "rev": "bc194f70731cc5d2b046a6c1b3b15f170f05999c", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "nixos", 14 | "repo": "nixpkgs", 15 | "rev": "bc194f70731cc5d2b046a6c1b3b15f170f05999c", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A Nix-flake-based Node.js development environment"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs?rev=bc194f70731cc5d2b046a6c1b3b15f170f05999c"; 6 | }; 7 | 8 | outputs = { nixpkgs, ... }: 9 | let 10 | system = "x86_64-linux"; 11 | in 12 | { 13 | devShells."${system}".default = 14 | let 15 | pkgs = import nixpkgs { 16 | inherit system; 17 | }; 18 | in 19 | pkgs.mkShell { 20 | # create an environment with nodejs_18, pnpm, and yarn 21 | packages = with pkgs; [ 22 | nodejs_18 23 | nodePackages.pnpm 24 | ]; 25 | 26 | shellHook = '' 27 | echo "node `${pkgs.nodejs}/bin/node --version`" 28 | ''; 29 | }; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "lazy-cached-vault-load", 3 | "name": "Lazy Cached Vault Load", 4 | "version": "0.0.6", 5 | "minAppVersion": "1.8.0", 6 | "description": "Achieve under 3 seconds load time for 30k+ files vault on mobile by loading file tree from cache and reconciling changes from filesystem in background.", 7 | "author": "d7sd6u", 8 | "authorUrl": "https://github.com/d7sd6u", 9 | "fundingUrl": "https://ko-fi.com/d7sd6u", 10 | "isDesktopOnly": false 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lazy-cached-vault-load", 3 | "version": "0.0.6", 4 | "description": "", 5 | "main": "./src/main.ts", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "node esbuild.config.mjs production", 9 | "typecheck": "tsc -noEmit -skipLibCheck", 10 | "upload-mobile": "adb push dist/* /storage/emulated/0/Documents/Vault/.obsidian/plugins/lazy-cached-vault-load", 11 | "version": "node version-bump.mjs && git add manifest.json versions.json", 12 | "lint": "eslint src/", 13 | "lint:fix": "eslint --fix src/", 14 | "format": "prettier --write .", 15 | "format:check": "prettier --check ." 16 | }, 17 | "keywords": [], 18 | "author": "", 19 | "license": "LGPL-3.0-only", 20 | "devDependencies": { 21 | "@eslint/js": "^9.20.0", 22 | "@tsconfig/strictest": "^2.0.5", 23 | "@types/node": "^16.11.6", 24 | "builtin-modules": "3.3.0", 25 | "esbuild": "0.17.3", 26 | "eslint": "^9.20.1", 27 | "obsidian": "1.7.2", 28 | "prettier": "^3.2.5", 29 | "typescript": "^5.7.3", 30 | "typescript-eslint": "^8.24.1" 31 | }, 32 | "dependencies": { 33 | "monkey-around": "^3.0.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | dependencies: 8 | monkey-around: 9 | specifier: ^3.0.0 10 | version: 3.0.0 11 | 12 | devDependencies: 13 | '@eslint/js': 14 | specifier: ^9.20.0 15 | version: 9.20.0 16 | '@tsconfig/strictest': 17 | specifier: ^2.0.5 18 | version: 2.0.5 19 | '@types/node': 20 | specifier: ^16.11.6 21 | version: 16.18.126 22 | builtin-modules: 23 | specifier: 3.3.0 24 | version: 3.3.0 25 | esbuild: 26 | specifier: 0.17.3 27 | version: 0.17.3 28 | eslint: 29 | specifier: ^9.20.1 30 | version: 9.20.1 31 | obsidian: 32 | specifier: 1.7.2 33 | version: 1.7.2(@codemirror/state@6.5.2)(@codemirror/view@6.36.3) 34 | prettier: 35 | specifier: ^3.2.5 36 | version: 3.5.1 37 | typescript: 38 | specifier: ^5.7.3 39 | version: 5.7.3 40 | typescript-eslint: 41 | specifier: ^8.24.1 42 | version: 8.24.1(eslint@9.20.1)(typescript@5.7.3) 43 | 44 | packages: 45 | 46 | /@codemirror/state@6.5.2: 47 | resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} 48 | dependencies: 49 | '@marijn/find-cluster-break': 1.0.2 50 | dev: true 51 | 52 | /@codemirror/view@6.36.3: 53 | resolution: {integrity: sha512-N2bilM47QWC8Hnx0rMdDxO2x2ImJ1FvZWXubwKgjeoOrWwEiFrtpA7SFHcuZ+o2Ze2VzbkgbzWVj4+V18LVkeg==} 54 | dependencies: 55 | '@codemirror/state': 6.5.2 56 | style-mod: 4.1.2 57 | w3c-keyname: 2.2.8 58 | dev: true 59 | 60 | /@esbuild/android-arm64@0.17.3: 61 | resolution: {integrity: sha512-XvJsYo3dO3Pi4kpalkyMvfQsjxPWHYjoX4MDiB/FUM4YMfWcXa5l4VCwFWVYI1+92yxqjuqrhNg0CZg3gSouyQ==} 62 | engines: {node: '>=12'} 63 | cpu: [arm64] 64 | os: [android] 65 | requiresBuild: true 66 | dev: true 67 | optional: true 68 | 69 | /@esbuild/android-arm@0.17.3: 70 | resolution: {integrity: sha512-1Mlz934GvbgdDmt26rTLmf03cAgLg5HyOgJN+ZGCeP3Q9ynYTNMn2/LQxIl7Uy+o4K6Rfi2OuLsr12JQQR8gNg==} 71 | engines: {node: '>=12'} 72 | cpu: [arm] 73 | os: [android] 74 | requiresBuild: true 75 | dev: true 76 | optional: true 77 | 78 | /@esbuild/android-x64@0.17.3: 79 | resolution: {integrity: sha512-nuV2CmLS07Gqh5/GrZLuqkU9Bm6H6vcCspM+zjp9TdQlxJtIe+qqEXQChmfc7nWdyr/yz3h45Utk1tUn8Cz5+A==} 80 | engines: {node: '>=12'} 81 | cpu: [x64] 82 | os: [android] 83 | requiresBuild: true 84 | dev: true 85 | optional: true 86 | 87 | /@esbuild/darwin-arm64@0.17.3: 88 | resolution: {integrity: sha512-01Hxaaat6m0Xp9AXGM8mjFtqqwDjzlMP0eQq9zll9U85ttVALGCGDuEvra5Feu/NbP5AEP1MaopPwzsTcUq1cw==} 89 | engines: {node: '>=12'} 90 | cpu: [arm64] 91 | os: [darwin] 92 | requiresBuild: true 93 | dev: true 94 | optional: true 95 | 96 | /@esbuild/darwin-x64@0.17.3: 97 | resolution: {integrity: sha512-Eo2gq0Q/er2muf8Z83X21UFoB7EU6/m3GNKvrhACJkjVThd0uA+8RfKpfNhuMCl1bKRfBzKOk6xaYKQZ4lZqvA==} 98 | engines: {node: '>=12'} 99 | cpu: [x64] 100 | os: [darwin] 101 | requiresBuild: true 102 | dev: true 103 | optional: true 104 | 105 | /@esbuild/freebsd-arm64@0.17.3: 106 | resolution: {integrity: sha512-CN62ESxaquP61n1ZjQP/jZte8CE09M6kNn3baos2SeUfdVBkWN5n6vGp2iKyb/bm/x4JQzEvJgRHLGd5F5b81w==} 107 | engines: {node: '>=12'} 108 | cpu: [arm64] 109 | os: [freebsd] 110 | requiresBuild: true 111 | dev: true 112 | optional: true 113 | 114 | /@esbuild/freebsd-x64@0.17.3: 115 | resolution: {integrity: sha512-feq+K8TxIznZE+zhdVurF3WNJ/Sa35dQNYbaqM/wsCbWdzXr5lyq+AaTUSER2cUR+SXPnd/EY75EPRjf4s1SLg==} 116 | engines: {node: '>=12'} 117 | cpu: [x64] 118 | os: [freebsd] 119 | requiresBuild: true 120 | dev: true 121 | optional: true 122 | 123 | /@esbuild/linux-arm64@0.17.3: 124 | resolution: {integrity: sha512-JHeZXD4auLYBnrKn6JYJ0o5nWJI9PhChA/Nt0G4MvLaMrvXuWnY93R3a7PiXeJQphpL1nYsaMcoV2QtuvRnF/g==} 125 | engines: {node: '>=12'} 126 | cpu: [arm64] 127 | os: [linux] 128 | requiresBuild: true 129 | dev: true 130 | optional: true 131 | 132 | /@esbuild/linux-arm@0.17.3: 133 | resolution: {integrity: sha512-CLP3EgyNuPcg2cshbwkqYy5bbAgK+VhyfMU7oIYyn+x4Y67xb5C5ylxsNUjRmr8BX+MW3YhVNm6Lq6FKtRTWHQ==} 134 | engines: {node: '>=12'} 135 | cpu: [arm] 136 | os: [linux] 137 | requiresBuild: true 138 | dev: true 139 | optional: true 140 | 141 | /@esbuild/linux-ia32@0.17.3: 142 | resolution: {integrity: sha512-FyXlD2ZjZqTFh0sOQxFDiWG1uQUEOLbEh9gKN/7pFxck5Vw0qjWSDqbn6C10GAa1rXJpwsntHcmLqydY9ST9ZA==} 143 | engines: {node: '>=12'} 144 | cpu: [ia32] 145 | os: [linux] 146 | requiresBuild: true 147 | dev: true 148 | optional: true 149 | 150 | /@esbuild/linux-loong64@0.17.3: 151 | resolution: {integrity: sha512-OrDGMvDBI2g7s04J8dh8/I7eSO+/E7nMDT2Z5IruBfUO/RiigF1OF6xoH33Dn4W/OwAWSUf1s2nXamb28ZklTA==} 152 | engines: {node: '>=12'} 153 | cpu: [loong64] 154 | os: [linux] 155 | requiresBuild: true 156 | dev: true 157 | optional: true 158 | 159 | /@esbuild/linux-mips64el@0.17.3: 160 | resolution: {integrity: sha512-DcnUpXnVCJvmv0TzuLwKBC2nsQHle8EIiAJiJ+PipEVC16wHXaPEKP0EqN8WnBe0TPvMITOUlP2aiL5YMld+CQ==} 161 | engines: {node: '>=12'} 162 | cpu: [mips64el] 163 | os: [linux] 164 | requiresBuild: true 165 | dev: true 166 | optional: true 167 | 168 | /@esbuild/linux-ppc64@0.17.3: 169 | resolution: {integrity: sha512-BDYf/l1WVhWE+FHAW3FzZPtVlk9QsrwsxGzABmN4g8bTjmhazsId3h127pliDRRu5674k1Y2RWejbpN46N9ZhQ==} 170 | engines: {node: '>=12'} 171 | cpu: [ppc64] 172 | os: [linux] 173 | requiresBuild: true 174 | dev: true 175 | optional: true 176 | 177 | /@esbuild/linux-riscv64@0.17.3: 178 | resolution: {integrity: sha512-WViAxWYMRIi+prTJTyV1wnqd2mS2cPqJlN85oscVhXdb/ZTFJdrpaqm/uDsZPGKHtbg5TuRX/ymKdOSk41YZow==} 179 | engines: {node: '>=12'} 180 | cpu: [riscv64] 181 | os: [linux] 182 | requiresBuild: true 183 | dev: true 184 | optional: true 185 | 186 | /@esbuild/linux-s390x@0.17.3: 187 | resolution: {integrity: sha512-Iw8lkNHUC4oGP1O/KhumcVy77u2s6+KUjieUqzEU3XuWJqZ+AY7uVMrrCbAiwWTkpQHkr00BuXH5RpC6Sb/7Ug==} 188 | engines: {node: '>=12'} 189 | cpu: [s390x] 190 | os: [linux] 191 | requiresBuild: true 192 | dev: true 193 | optional: true 194 | 195 | /@esbuild/linux-x64@0.17.3: 196 | resolution: {integrity: sha512-0AGkWQMzeoeAtXQRNB3s4J1/T2XbigM2/Mn2yU1tQSmQRmHIZdkGbVq2A3aDdNslPyhb9/lH0S5GMTZ4xsjBqg==} 197 | engines: {node: '>=12'} 198 | cpu: [x64] 199 | os: [linux] 200 | requiresBuild: true 201 | dev: true 202 | optional: true 203 | 204 | /@esbuild/netbsd-x64@0.17.3: 205 | resolution: {integrity: sha512-4+rR/WHOxIVh53UIQIICryjdoKdHsFZFD4zLSonJ9RRw7bhKzVyXbnRPsWSfwybYqw9sB7ots/SYyufL1mBpEg==} 206 | engines: {node: '>=12'} 207 | cpu: [x64] 208 | os: [netbsd] 209 | requiresBuild: true 210 | dev: true 211 | optional: true 212 | 213 | /@esbuild/openbsd-x64@0.17.3: 214 | resolution: {integrity: sha512-cVpWnkx9IYg99EjGxa5Gc0XmqumtAwK3aoz7O4Dii2vko+qXbkHoujWA68cqXjhh6TsLaQelfDO4MVnyr+ODeA==} 215 | engines: {node: '>=12'} 216 | cpu: [x64] 217 | os: [openbsd] 218 | requiresBuild: true 219 | dev: true 220 | optional: true 221 | 222 | /@esbuild/sunos-x64@0.17.3: 223 | resolution: {integrity: sha512-RxmhKLbTCDAY2xOfrww6ieIZkZF+KBqG7S2Ako2SljKXRFi+0863PspK74QQ7JpmWwncChY25JTJSbVBYGQk2Q==} 224 | engines: {node: '>=12'} 225 | cpu: [x64] 226 | os: [sunos] 227 | requiresBuild: true 228 | dev: true 229 | optional: true 230 | 231 | /@esbuild/win32-arm64@0.17.3: 232 | resolution: {integrity: sha512-0r36VeEJ4efwmofxVJRXDjVRP2jTmv877zc+i+Pc7MNsIr38NfsjkQj23AfF7l0WbB+RQ7VUb+LDiqC/KY/M/A==} 233 | engines: {node: '>=12'} 234 | cpu: [arm64] 235 | os: [win32] 236 | requiresBuild: true 237 | dev: true 238 | optional: true 239 | 240 | /@esbuild/win32-ia32@0.17.3: 241 | resolution: {integrity: sha512-wgO6rc7uGStH22nur4aLFcq7Wh86bE9cOFmfTr/yxN3BXvDEdCSXyKkO+U5JIt53eTOgC47v9k/C1bITWL/Teg==} 242 | engines: {node: '>=12'} 243 | cpu: [ia32] 244 | os: [win32] 245 | requiresBuild: true 246 | dev: true 247 | optional: true 248 | 249 | /@esbuild/win32-x64@0.17.3: 250 | resolution: {integrity: sha512-FdVl64OIuiKjgXBjwZaJLKp0eaEckifbhn10dXWhysMJkWblg3OEEGKSIyhiD5RSgAya8WzP3DNkngtIg3Nt7g==} 251 | engines: {node: '>=12'} 252 | cpu: [x64] 253 | os: [win32] 254 | requiresBuild: true 255 | dev: true 256 | optional: true 257 | 258 | /@eslint-community/eslint-utils@4.4.1(eslint@9.20.1): 259 | resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} 260 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 261 | peerDependencies: 262 | eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 263 | dependencies: 264 | eslint: 9.20.1 265 | eslint-visitor-keys: 3.4.3 266 | dev: true 267 | 268 | /@eslint-community/regexpp@4.12.1: 269 | resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} 270 | engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 271 | dev: true 272 | 273 | /@eslint/config-array@0.19.2: 274 | resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} 275 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 276 | dependencies: 277 | '@eslint/object-schema': 2.1.6 278 | debug: 4.4.0 279 | minimatch: 3.1.2 280 | transitivePeerDependencies: 281 | - supports-color 282 | dev: true 283 | 284 | /@eslint/core@0.10.0: 285 | resolution: {integrity: sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==} 286 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 287 | dependencies: 288 | '@types/json-schema': 7.0.15 289 | dev: true 290 | 291 | /@eslint/core@0.11.0: 292 | resolution: {integrity: sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==} 293 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 294 | dependencies: 295 | '@types/json-schema': 7.0.15 296 | dev: true 297 | 298 | /@eslint/eslintrc@3.2.0: 299 | resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} 300 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 301 | dependencies: 302 | ajv: 6.12.6 303 | debug: 4.4.0 304 | espree: 10.3.0 305 | globals: 14.0.0 306 | ignore: 5.3.2 307 | import-fresh: 3.3.1 308 | js-yaml: 4.1.0 309 | minimatch: 3.1.2 310 | strip-json-comments: 3.1.1 311 | transitivePeerDependencies: 312 | - supports-color 313 | dev: true 314 | 315 | /@eslint/js@9.20.0: 316 | resolution: {integrity: sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==} 317 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 318 | dev: true 319 | 320 | /@eslint/object-schema@2.1.6: 321 | resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} 322 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 323 | dev: true 324 | 325 | /@eslint/plugin-kit@0.2.5: 326 | resolution: {integrity: sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==} 327 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 328 | dependencies: 329 | '@eslint/core': 0.10.0 330 | levn: 0.4.1 331 | dev: true 332 | 333 | /@humanfs/core@0.19.1: 334 | resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} 335 | engines: {node: '>=18.18.0'} 336 | dev: true 337 | 338 | /@humanfs/node@0.16.6: 339 | resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} 340 | engines: {node: '>=18.18.0'} 341 | dependencies: 342 | '@humanfs/core': 0.19.1 343 | '@humanwhocodes/retry': 0.3.1 344 | dev: true 345 | 346 | /@humanwhocodes/module-importer@1.0.1: 347 | resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 348 | engines: {node: '>=12.22'} 349 | dev: true 350 | 351 | /@humanwhocodes/retry@0.3.1: 352 | resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} 353 | engines: {node: '>=18.18'} 354 | dev: true 355 | 356 | /@humanwhocodes/retry@0.4.1: 357 | resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} 358 | engines: {node: '>=18.18'} 359 | dev: true 360 | 361 | /@marijn/find-cluster-break@1.0.2: 362 | resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} 363 | dev: true 364 | 365 | /@nodelib/fs.scandir@2.1.5: 366 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 367 | engines: {node: '>= 8'} 368 | dependencies: 369 | '@nodelib/fs.stat': 2.0.5 370 | run-parallel: 1.2.0 371 | dev: true 372 | 373 | /@nodelib/fs.stat@2.0.5: 374 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 375 | engines: {node: '>= 8'} 376 | dev: true 377 | 378 | /@nodelib/fs.walk@1.2.8: 379 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 380 | engines: {node: '>= 8'} 381 | dependencies: 382 | '@nodelib/fs.scandir': 2.1.5 383 | fastq: 1.19.0 384 | dev: true 385 | 386 | /@tsconfig/strictest@2.0.5: 387 | resolution: {integrity: sha512-ec4tjL2Rr0pkZ5hww65c+EEPYwxOi4Ryv+0MtjeaSQRJyq322Q27eOQiFbuNgw2hpL4hB1/W/HBGk3VKS43osg==} 388 | dev: true 389 | 390 | /@types/codemirror@5.60.8: 391 | resolution: {integrity: sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==} 392 | dependencies: 393 | '@types/tern': 0.23.9 394 | dev: true 395 | 396 | /@types/estree@1.0.6: 397 | resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} 398 | dev: true 399 | 400 | /@types/json-schema@7.0.15: 401 | resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 402 | dev: true 403 | 404 | /@types/node@16.18.126: 405 | resolution: {integrity: sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==} 406 | dev: true 407 | 408 | /@types/tern@0.23.9: 409 | resolution: {integrity: sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==} 410 | dependencies: 411 | '@types/estree': 1.0.6 412 | dev: true 413 | 414 | /@typescript-eslint/eslint-plugin@8.24.1(@typescript-eslint/parser@8.24.1)(eslint@9.20.1)(typescript@5.7.3): 415 | resolution: {integrity: sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==} 416 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 417 | peerDependencies: 418 | '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 419 | eslint: ^8.57.0 || ^9.0.0 420 | typescript: '>=4.8.4 <5.8.0' 421 | dependencies: 422 | '@eslint-community/regexpp': 4.12.1 423 | '@typescript-eslint/parser': 8.24.1(eslint@9.20.1)(typescript@5.7.3) 424 | '@typescript-eslint/scope-manager': 8.24.1 425 | '@typescript-eslint/type-utils': 8.24.1(eslint@9.20.1)(typescript@5.7.3) 426 | '@typescript-eslint/utils': 8.24.1(eslint@9.20.1)(typescript@5.7.3) 427 | '@typescript-eslint/visitor-keys': 8.24.1 428 | eslint: 9.20.1 429 | graphemer: 1.4.0 430 | ignore: 5.3.2 431 | natural-compare: 1.4.0 432 | ts-api-utils: 2.0.1(typescript@5.7.3) 433 | typescript: 5.7.3 434 | transitivePeerDependencies: 435 | - supports-color 436 | dev: true 437 | 438 | /@typescript-eslint/parser@8.24.1(eslint@9.20.1)(typescript@5.7.3): 439 | resolution: {integrity: sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==} 440 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 441 | peerDependencies: 442 | eslint: ^8.57.0 || ^9.0.0 443 | typescript: '>=4.8.4 <5.8.0' 444 | dependencies: 445 | '@typescript-eslint/scope-manager': 8.24.1 446 | '@typescript-eslint/types': 8.24.1 447 | '@typescript-eslint/typescript-estree': 8.24.1(typescript@5.7.3) 448 | '@typescript-eslint/visitor-keys': 8.24.1 449 | debug: 4.4.0 450 | eslint: 9.20.1 451 | typescript: 5.7.3 452 | transitivePeerDependencies: 453 | - supports-color 454 | dev: true 455 | 456 | /@typescript-eslint/scope-manager@8.24.1: 457 | resolution: {integrity: sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==} 458 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 459 | dependencies: 460 | '@typescript-eslint/types': 8.24.1 461 | '@typescript-eslint/visitor-keys': 8.24.1 462 | dev: true 463 | 464 | /@typescript-eslint/type-utils@8.24.1(eslint@9.20.1)(typescript@5.7.3): 465 | resolution: {integrity: sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==} 466 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 467 | peerDependencies: 468 | eslint: ^8.57.0 || ^9.0.0 469 | typescript: '>=4.8.4 <5.8.0' 470 | dependencies: 471 | '@typescript-eslint/typescript-estree': 8.24.1(typescript@5.7.3) 472 | '@typescript-eslint/utils': 8.24.1(eslint@9.20.1)(typescript@5.7.3) 473 | debug: 4.4.0 474 | eslint: 9.20.1 475 | ts-api-utils: 2.0.1(typescript@5.7.3) 476 | typescript: 5.7.3 477 | transitivePeerDependencies: 478 | - supports-color 479 | dev: true 480 | 481 | /@typescript-eslint/types@8.24.1: 482 | resolution: {integrity: sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==} 483 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 484 | dev: true 485 | 486 | /@typescript-eslint/typescript-estree@8.24.1(typescript@5.7.3): 487 | resolution: {integrity: sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==} 488 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 489 | peerDependencies: 490 | typescript: '>=4.8.4 <5.8.0' 491 | dependencies: 492 | '@typescript-eslint/types': 8.24.1 493 | '@typescript-eslint/visitor-keys': 8.24.1 494 | debug: 4.4.0 495 | fast-glob: 3.3.3 496 | is-glob: 4.0.3 497 | minimatch: 9.0.5 498 | semver: 7.7.1 499 | ts-api-utils: 2.0.1(typescript@5.7.3) 500 | typescript: 5.7.3 501 | transitivePeerDependencies: 502 | - supports-color 503 | dev: true 504 | 505 | /@typescript-eslint/utils@8.24.1(eslint@9.20.1)(typescript@5.7.3): 506 | resolution: {integrity: sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==} 507 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 508 | peerDependencies: 509 | eslint: ^8.57.0 || ^9.0.0 510 | typescript: '>=4.8.4 <5.8.0' 511 | dependencies: 512 | '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1) 513 | '@typescript-eslint/scope-manager': 8.24.1 514 | '@typescript-eslint/types': 8.24.1 515 | '@typescript-eslint/typescript-estree': 8.24.1(typescript@5.7.3) 516 | eslint: 9.20.1 517 | typescript: 5.7.3 518 | transitivePeerDependencies: 519 | - supports-color 520 | dev: true 521 | 522 | /@typescript-eslint/visitor-keys@8.24.1: 523 | resolution: {integrity: sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==} 524 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 525 | dependencies: 526 | '@typescript-eslint/types': 8.24.1 527 | eslint-visitor-keys: 4.2.0 528 | dev: true 529 | 530 | /acorn-jsx@5.3.2(acorn@8.14.0): 531 | resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 532 | peerDependencies: 533 | acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 534 | dependencies: 535 | acorn: 8.14.0 536 | dev: true 537 | 538 | /acorn@8.14.0: 539 | resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} 540 | engines: {node: '>=0.4.0'} 541 | hasBin: true 542 | dev: true 543 | 544 | /ajv@6.12.6: 545 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 546 | dependencies: 547 | fast-deep-equal: 3.1.3 548 | fast-json-stable-stringify: 2.1.0 549 | json-schema-traverse: 0.4.1 550 | uri-js: 4.4.1 551 | dev: true 552 | 553 | /ansi-styles@4.3.0: 554 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 555 | engines: {node: '>=8'} 556 | dependencies: 557 | color-convert: 2.0.1 558 | dev: true 559 | 560 | /argparse@2.0.1: 561 | resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 562 | dev: true 563 | 564 | /balanced-match@1.0.2: 565 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 566 | dev: true 567 | 568 | /brace-expansion@1.1.11: 569 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 570 | dependencies: 571 | balanced-match: 1.0.2 572 | concat-map: 0.0.1 573 | dev: true 574 | 575 | /brace-expansion@2.0.1: 576 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 577 | dependencies: 578 | balanced-match: 1.0.2 579 | dev: true 580 | 581 | /braces@3.0.3: 582 | resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 583 | engines: {node: '>=8'} 584 | dependencies: 585 | fill-range: 7.1.1 586 | dev: true 587 | 588 | /builtin-modules@3.3.0: 589 | resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} 590 | engines: {node: '>=6'} 591 | dev: true 592 | 593 | /callsites@3.1.0: 594 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 595 | engines: {node: '>=6'} 596 | dev: true 597 | 598 | /chalk@4.1.2: 599 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 600 | engines: {node: '>=10'} 601 | dependencies: 602 | ansi-styles: 4.3.0 603 | supports-color: 7.2.0 604 | dev: true 605 | 606 | /color-convert@2.0.1: 607 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 608 | engines: {node: '>=7.0.0'} 609 | dependencies: 610 | color-name: 1.1.4 611 | dev: true 612 | 613 | /color-name@1.1.4: 614 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 615 | dev: true 616 | 617 | /concat-map@0.0.1: 618 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 619 | dev: true 620 | 621 | /cross-spawn@7.0.6: 622 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 623 | engines: {node: '>= 8'} 624 | dependencies: 625 | path-key: 3.1.1 626 | shebang-command: 2.0.0 627 | which: 2.0.2 628 | dev: true 629 | 630 | /debug@4.4.0: 631 | resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} 632 | engines: {node: '>=6.0'} 633 | peerDependencies: 634 | supports-color: '*' 635 | peerDependenciesMeta: 636 | supports-color: 637 | optional: true 638 | dependencies: 639 | ms: 2.1.3 640 | dev: true 641 | 642 | /deep-is@0.1.4: 643 | resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 644 | dev: true 645 | 646 | /esbuild@0.17.3: 647 | resolution: {integrity: sha512-9n3AsBRe6sIyOc6kmoXg2ypCLgf3eZSraWFRpnkto+svt8cZNuKTkb1bhQcitBcvIqjNiK7K0J3KPmwGSfkA8g==} 648 | engines: {node: '>=12'} 649 | hasBin: true 650 | requiresBuild: true 651 | optionalDependencies: 652 | '@esbuild/android-arm': 0.17.3 653 | '@esbuild/android-arm64': 0.17.3 654 | '@esbuild/android-x64': 0.17.3 655 | '@esbuild/darwin-arm64': 0.17.3 656 | '@esbuild/darwin-x64': 0.17.3 657 | '@esbuild/freebsd-arm64': 0.17.3 658 | '@esbuild/freebsd-x64': 0.17.3 659 | '@esbuild/linux-arm': 0.17.3 660 | '@esbuild/linux-arm64': 0.17.3 661 | '@esbuild/linux-ia32': 0.17.3 662 | '@esbuild/linux-loong64': 0.17.3 663 | '@esbuild/linux-mips64el': 0.17.3 664 | '@esbuild/linux-ppc64': 0.17.3 665 | '@esbuild/linux-riscv64': 0.17.3 666 | '@esbuild/linux-s390x': 0.17.3 667 | '@esbuild/linux-x64': 0.17.3 668 | '@esbuild/netbsd-x64': 0.17.3 669 | '@esbuild/openbsd-x64': 0.17.3 670 | '@esbuild/sunos-x64': 0.17.3 671 | '@esbuild/win32-arm64': 0.17.3 672 | '@esbuild/win32-ia32': 0.17.3 673 | '@esbuild/win32-x64': 0.17.3 674 | dev: true 675 | 676 | /escape-string-regexp@4.0.0: 677 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 678 | engines: {node: '>=10'} 679 | dev: true 680 | 681 | /eslint-scope@8.2.0: 682 | resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} 683 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 684 | dependencies: 685 | esrecurse: 4.3.0 686 | estraverse: 5.3.0 687 | dev: true 688 | 689 | /eslint-visitor-keys@3.4.3: 690 | resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 691 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 692 | dev: true 693 | 694 | /eslint-visitor-keys@4.2.0: 695 | resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} 696 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 697 | dev: true 698 | 699 | /eslint@9.20.1: 700 | resolution: {integrity: sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==} 701 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 702 | hasBin: true 703 | peerDependencies: 704 | jiti: '*' 705 | peerDependenciesMeta: 706 | jiti: 707 | optional: true 708 | dependencies: 709 | '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1) 710 | '@eslint-community/regexpp': 4.12.1 711 | '@eslint/config-array': 0.19.2 712 | '@eslint/core': 0.11.0 713 | '@eslint/eslintrc': 3.2.0 714 | '@eslint/js': 9.20.0 715 | '@eslint/plugin-kit': 0.2.5 716 | '@humanfs/node': 0.16.6 717 | '@humanwhocodes/module-importer': 1.0.1 718 | '@humanwhocodes/retry': 0.4.1 719 | '@types/estree': 1.0.6 720 | '@types/json-schema': 7.0.15 721 | ajv: 6.12.6 722 | chalk: 4.1.2 723 | cross-spawn: 7.0.6 724 | debug: 4.4.0 725 | escape-string-regexp: 4.0.0 726 | eslint-scope: 8.2.0 727 | eslint-visitor-keys: 4.2.0 728 | espree: 10.3.0 729 | esquery: 1.6.0 730 | esutils: 2.0.3 731 | fast-deep-equal: 3.1.3 732 | file-entry-cache: 8.0.0 733 | find-up: 5.0.0 734 | glob-parent: 6.0.2 735 | ignore: 5.3.2 736 | imurmurhash: 0.1.4 737 | is-glob: 4.0.3 738 | json-stable-stringify-without-jsonify: 1.0.1 739 | lodash.merge: 4.6.2 740 | minimatch: 3.1.2 741 | natural-compare: 1.4.0 742 | optionator: 0.9.4 743 | transitivePeerDependencies: 744 | - supports-color 745 | dev: true 746 | 747 | /espree@10.3.0: 748 | resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} 749 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 750 | dependencies: 751 | acorn: 8.14.0 752 | acorn-jsx: 5.3.2(acorn@8.14.0) 753 | eslint-visitor-keys: 4.2.0 754 | dev: true 755 | 756 | /esquery@1.6.0: 757 | resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} 758 | engines: {node: '>=0.10'} 759 | dependencies: 760 | estraverse: 5.3.0 761 | dev: true 762 | 763 | /esrecurse@4.3.0: 764 | resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 765 | engines: {node: '>=4.0'} 766 | dependencies: 767 | estraverse: 5.3.0 768 | dev: true 769 | 770 | /estraverse@5.3.0: 771 | resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 772 | engines: {node: '>=4.0'} 773 | dev: true 774 | 775 | /esutils@2.0.3: 776 | resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 777 | engines: {node: '>=0.10.0'} 778 | dev: true 779 | 780 | /fast-deep-equal@3.1.3: 781 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 782 | dev: true 783 | 784 | /fast-glob@3.3.3: 785 | resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} 786 | engines: {node: '>=8.6.0'} 787 | dependencies: 788 | '@nodelib/fs.stat': 2.0.5 789 | '@nodelib/fs.walk': 1.2.8 790 | glob-parent: 5.1.2 791 | merge2: 1.4.1 792 | micromatch: 4.0.8 793 | dev: true 794 | 795 | /fast-json-stable-stringify@2.1.0: 796 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 797 | dev: true 798 | 799 | /fast-levenshtein@2.0.6: 800 | resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 801 | dev: true 802 | 803 | /fastq@1.19.0: 804 | resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==} 805 | dependencies: 806 | reusify: 1.0.4 807 | dev: true 808 | 809 | /file-entry-cache@8.0.0: 810 | resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} 811 | engines: {node: '>=16.0.0'} 812 | dependencies: 813 | flat-cache: 4.0.1 814 | dev: true 815 | 816 | /fill-range@7.1.1: 817 | resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 818 | engines: {node: '>=8'} 819 | dependencies: 820 | to-regex-range: 5.0.1 821 | dev: true 822 | 823 | /find-up@5.0.0: 824 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 825 | engines: {node: '>=10'} 826 | dependencies: 827 | locate-path: 6.0.0 828 | path-exists: 4.0.0 829 | dev: true 830 | 831 | /flat-cache@4.0.1: 832 | resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} 833 | engines: {node: '>=16'} 834 | dependencies: 835 | flatted: 3.3.3 836 | keyv: 4.5.4 837 | dev: true 838 | 839 | /flatted@3.3.3: 840 | resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} 841 | dev: true 842 | 843 | /glob-parent@5.1.2: 844 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 845 | engines: {node: '>= 6'} 846 | dependencies: 847 | is-glob: 4.0.3 848 | dev: true 849 | 850 | /glob-parent@6.0.2: 851 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 852 | engines: {node: '>=10.13.0'} 853 | dependencies: 854 | is-glob: 4.0.3 855 | dev: true 856 | 857 | /globals@14.0.0: 858 | resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} 859 | engines: {node: '>=18'} 860 | dev: true 861 | 862 | /graphemer@1.4.0: 863 | resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 864 | dev: true 865 | 866 | /has-flag@4.0.0: 867 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 868 | engines: {node: '>=8'} 869 | dev: true 870 | 871 | /ignore@5.3.2: 872 | resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} 873 | engines: {node: '>= 4'} 874 | dev: true 875 | 876 | /import-fresh@3.3.1: 877 | resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} 878 | engines: {node: '>=6'} 879 | dependencies: 880 | parent-module: 1.0.1 881 | resolve-from: 4.0.0 882 | dev: true 883 | 884 | /imurmurhash@0.1.4: 885 | resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 886 | engines: {node: '>=0.8.19'} 887 | dev: true 888 | 889 | /is-extglob@2.1.1: 890 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 891 | engines: {node: '>=0.10.0'} 892 | dev: true 893 | 894 | /is-glob@4.0.3: 895 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 896 | engines: {node: '>=0.10.0'} 897 | dependencies: 898 | is-extglob: 2.1.1 899 | dev: true 900 | 901 | /is-number@7.0.0: 902 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 903 | engines: {node: '>=0.12.0'} 904 | dev: true 905 | 906 | /isexe@2.0.0: 907 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 908 | dev: true 909 | 910 | /js-yaml@4.1.0: 911 | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 912 | hasBin: true 913 | dependencies: 914 | argparse: 2.0.1 915 | dev: true 916 | 917 | /json-buffer@3.0.1: 918 | resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 919 | dev: true 920 | 921 | /json-schema-traverse@0.4.1: 922 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 923 | dev: true 924 | 925 | /json-stable-stringify-without-jsonify@1.0.1: 926 | resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 927 | dev: true 928 | 929 | /keyv@4.5.4: 930 | resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 931 | dependencies: 932 | json-buffer: 3.0.1 933 | dev: true 934 | 935 | /levn@0.4.1: 936 | resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 937 | engines: {node: '>= 0.8.0'} 938 | dependencies: 939 | prelude-ls: 1.2.1 940 | type-check: 0.4.0 941 | dev: true 942 | 943 | /locate-path@6.0.0: 944 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 945 | engines: {node: '>=10'} 946 | dependencies: 947 | p-locate: 5.0.0 948 | dev: true 949 | 950 | /lodash.merge@4.6.2: 951 | resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 952 | dev: true 953 | 954 | /merge2@1.4.1: 955 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 956 | engines: {node: '>= 8'} 957 | dev: true 958 | 959 | /micromatch@4.0.8: 960 | resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} 961 | engines: {node: '>=8.6'} 962 | dependencies: 963 | braces: 3.0.3 964 | picomatch: 2.3.1 965 | dev: true 966 | 967 | /minimatch@3.1.2: 968 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 969 | dependencies: 970 | brace-expansion: 1.1.11 971 | dev: true 972 | 973 | /minimatch@9.0.5: 974 | resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 975 | engines: {node: '>=16 || 14 >=14.17'} 976 | dependencies: 977 | brace-expansion: 2.0.1 978 | dev: true 979 | 980 | /moment@2.29.4: 981 | resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} 982 | dev: true 983 | 984 | /monkey-around@3.0.0: 985 | resolution: {integrity: sha512-jL6uY2lEAoaHxZep1cNRkCZjoIWY4g5VYCjriEWmcyHU7w8NU1+JH57xE251UVTohK0lCxMjv0ZV4ByDLIXEpw==} 986 | dev: false 987 | 988 | /ms@2.1.3: 989 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 990 | dev: true 991 | 992 | /natural-compare@1.4.0: 993 | resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 994 | dev: true 995 | 996 | /obsidian@1.7.2(@codemirror/state@6.5.2)(@codemirror/view@6.36.3): 997 | resolution: {integrity: sha512-k9hN9brdknJC+afKr5FQzDRuEFGDKbDjfCazJwpgibwCAoZNYHYV8p/s3mM8I6AsnKrPKNXf8xGuMZ4enWelZQ==} 998 | peerDependencies: 999 | '@codemirror/state': ^6.0.0 1000 | '@codemirror/view': ^6.0.0 1001 | dependencies: 1002 | '@codemirror/state': 6.5.2 1003 | '@codemirror/view': 6.36.3 1004 | '@types/codemirror': 5.60.8 1005 | moment: 2.29.4 1006 | dev: true 1007 | 1008 | /optionator@0.9.4: 1009 | resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} 1010 | engines: {node: '>= 0.8.0'} 1011 | dependencies: 1012 | deep-is: 0.1.4 1013 | fast-levenshtein: 2.0.6 1014 | levn: 0.4.1 1015 | prelude-ls: 1.2.1 1016 | type-check: 0.4.0 1017 | word-wrap: 1.2.5 1018 | dev: true 1019 | 1020 | /p-limit@3.1.0: 1021 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 1022 | engines: {node: '>=10'} 1023 | dependencies: 1024 | yocto-queue: 0.1.0 1025 | dev: true 1026 | 1027 | /p-locate@5.0.0: 1028 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 1029 | engines: {node: '>=10'} 1030 | dependencies: 1031 | p-limit: 3.1.0 1032 | dev: true 1033 | 1034 | /parent-module@1.0.1: 1035 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1036 | engines: {node: '>=6'} 1037 | dependencies: 1038 | callsites: 3.1.0 1039 | dev: true 1040 | 1041 | /path-exists@4.0.0: 1042 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1043 | engines: {node: '>=8'} 1044 | dev: true 1045 | 1046 | /path-key@3.1.1: 1047 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1048 | engines: {node: '>=8'} 1049 | dev: true 1050 | 1051 | /picomatch@2.3.1: 1052 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1053 | engines: {node: '>=8.6'} 1054 | dev: true 1055 | 1056 | /prelude-ls@1.2.1: 1057 | resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 1058 | engines: {node: '>= 0.8.0'} 1059 | dev: true 1060 | 1061 | /prettier@3.5.1: 1062 | resolution: {integrity: sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==} 1063 | engines: {node: '>=14'} 1064 | hasBin: true 1065 | dev: true 1066 | 1067 | /punycode@2.3.1: 1068 | resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 1069 | engines: {node: '>=6'} 1070 | dev: true 1071 | 1072 | /queue-microtask@1.2.3: 1073 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 1074 | dev: true 1075 | 1076 | /resolve-from@4.0.0: 1077 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 1078 | engines: {node: '>=4'} 1079 | dev: true 1080 | 1081 | /reusify@1.0.4: 1082 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 1083 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1084 | dev: true 1085 | 1086 | /run-parallel@1.2.0: 1087 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1088 | dependencies: 1089 | queue-microtask: 1.2.3 1090 | dev: true 1091 | 1092 | /semver@7.7.1: 1093 | resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} 1094 | engines: {node: '>=10'} 1095 | hasBin: true 1096 | dev: true 1097 | 1098 | /shebang-command@2.0.0: 1099 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1100 | engines: {node: '>=8'} 1101 | dependencies: 1102 | shebang-regex: 3.0.0 1103 | dev: true 1104 | 1105 | /shebang-regex@3.0.0: 1106 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1107 | engines: {node: '>=8'} 1108 | dev: true 1109 | 1110 | /strip-json-comments@3.1.1: 1111 | resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 1112 | engines: {node: '>=8'} 1113 | dev: true 1114 | 1115 | /style-mod@4.1.2: 1116 | resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} 1117 | dev: true 1118 | 1119 | /supports-color@7.2.0: 1120 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1121 | engines: {node: '>=8'} 1122 | dependencies: 1123 | has-flag: 4.0.0 1124 | dev: true 1125 | 1126 | /to-regex-range@5.0.1: 1127 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1128 | engines: {node: '>=8.0'} 1129 | dependencies: 1130 | is-number: 7.0.0 1131 | dev: true 1132 | 1133 | /ts-api-utils@2.0.1(typescript@5.7.3): 1134 | resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==} 1135 | engines: {node: '>=18.12'} 1136 | peerDependencies: 1137 | typescript: '>=4.8.4' 1138 | dependencies: 1139 | typescript: 5.7.3 1140 | dev: true 1141 | 1142 | /type-check@0.4.0: 1143 | resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1144 | engines: {node: '>= 0.8.0'} 1145 | dependencies: 1146 | prelude-ls: 1.2.1 1147 | dev: true 1148 | 1149 | /typescript-eslint@8.24.1(eslint@9.20.1)(typescript@5.7.3): 1150 | resolution: {integrity: sha512-cw3rEdzDqBs70TIcb0Gdzbt6h11BSs2pS0yaq7hDWDBtCCSei1pPSUXE9qUdQ/Wm9NgFg8mKtMt1b8fTHIl1jA==} 1151 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1152 | peerDependencies: 1153 | eslint: ^8.57.0 || ^9.0.0 1154 | typescript: '>=4.8.4 <5.8.0' 1155 | dependencies: 1156 | '@typescript-eslint/eslint-plugin': 8.24.1(@typescript-eslint/parser@8.24.1)(eslint@9.20.1)(typescript@5.7.3) 1157 | '@typescript-eslint/parser': 8.24.1(eslint@9.20.1)(typescript@5.7.3) 1158 | '@typescript-eslint/utils': 8.24.1(eslint@9.20.1)(typescript@5.7.3) 1159 | eslint: 9.20.1 1160 | typescript: 5.7.3 1161 | transitivePeerDependencies: 1162 | - supports-color 1163 | dev: true 1164 | 1165 | /typescript@5.7.3: 1166 | resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} 1167 | engines: {node: '>=14.17'} 1168 | hasBin: true 1169 | dev: true 1170 | 1171 | /uri-js@4.4.1: 1172 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1173 | dependencies: 1174 | punycode: 2.3.1 1175 | dev: true 1176 | 1177 | /w3c-keyname@2.2.8: 1178 | resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} 1179 | dev: true 1180 | 1181 | /which@2.0.2: 1182 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1183 | engines: {node: '>= 8'} 1184 | hasBin: true 1185 | dependencies: 1186 | isexe: 2.0.0 1187 | dev: true 1188 | 1189 | /word-wrap@1.2.5: 1190 | resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} 1191 | engines: {node: '>=0.10.0'} 1192 | dev: true 1193 | 1194 | /yocto-queue@0.1.0: 1195 | resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1196 | engines: {node: '>=10'} 1197 | dev: true 1198 | -------------------------------------------------------------------------------- /src/FSCache.ts: -------------------------------------------------------------------------------- 1 | import { debounce } from "obsidian"; 2 | 3 | /* eslint-disable @typescript-eslint/prefer-promise-reject-errors */ 4 | export class FSCache { 5 | private dbName: string; 6 | private storeName: string; 7 | private db: IDBDatabase | null = null; 8 | 9 | constructor(vaultPath: string) { 10 | this.dbName = "FSCache-" + vaultPath; 11 | this.storeName = "tree"; 12 | } 13 | 14 | async init(): Promise { 15 | return new Promise((resolve, reject) => { 16 | const request = indexedDB.open(this.dbName, 4); 17 | let isUpgraded = false; 18 | 19 | request.onupgradeneeded = (event) => { 20 | isUpgraded = true; 21 | const db = (event.target as IDBOpenDBRequest).result; 22 | if (db.objectStoreNames.contains(this.storeName)) { 23 | db.deleteObjectStore(this.storeName); 24 | } 25 | db.createObjectStore(this.storeName, { 26 | keyPath: "key" satisfies keyof CacheTreeFolder, 27 | }); 28 | }; 29 | 30 | request.onsuccess = () => { 31 | this.db = request.result; 32 | if (isUpgraded) { 33 | void this.clearAll().then(() => { 34 | resolve(true); 35 | }); 36 | } else resolve(false); 37 | }; 38 | 39 | request.onerror = () => { 40 | reject(request.error); 41 | }; 42 | }); 43 | } 44 | 45 | private getStore(mode: IDBTransactionMode): IDBObjectStore { 46 | if (!this.db) throw new Error("Database not initialized"); 47 | return this.db 48 | .transaction(this.storeName, mode) 49 | .objectStore(this.storeName); 50 | } 51 | 52 | flushEdits = debounce( 53 | (tree: CacheTreeFolder) => { 54 | return new Promise((resolve, reject) => { 55 | const request = this.getStore("readwrite").put(tree); 56 | request.onsuccess = () => { 57 | new Notice("Updated cache"); 58 | resolve(); 59 | }; 60 | request.onerror = () => { 61 | reject(request.error); 62 | }; 63 | }); 64 | }, 65 | 1000, 66 | true, 67 | ); 68 | 69 | private getByPath( 70 | path: string, 71 | ): CacheTreeFile | CacheTreeFolder | undefined { 72 | if (path === "/") return this.inMemoryTree; 73 | 74 | let currentFolder: CacheTreeFolder | CacheTreeFile | undefined = 75 | this.inMemoryTree; 76 | for (const section of path.split("/")) { 77 | if (!("children" in currentFolder)) return undefined; 78 | const next: CacheTreeFile | CacheTreeFolder | undefined = 79 | currentFolder.children[section]; 80 | if (!next) return undefined; 81 | currentFolder = next; 82 | } 83 | return currentFolder; 84 | } 85 | 86 | addItem( 87 | parentPath: string, 88 | file: CacheTreeFile | CacheTreeFolder, 89 | name: string, 90 | ) { 91 | const currentFolder = this.getByPath(parentPath); 92 | if ( 93 | !currentFolder || 94 | !("children" in currentFolder) || 95 | name in currentFolder.children 96 | ) 97 | return; 98 | currentFolder.children[name] = file; 99 | this.changeTree(this.inMemoryTree); 100 | } 101 | getItem(path: string) { 102 | return this.getByPath(path); 103 | } 104 | 105 | async getTree(): Promise { 106 | const tree = await new Promise((resolve, reject) => { 107 | const request = this.getStore("readonly").get("tree"); 108 | request.onsuccess = () => { 109 | resolve(request.result as CacheTreeFolder); 110 | }; 111 | request.onerror = () => { 112 | reject(request.error); 113 | }; 114 | }); 115 | this.inMemoryTree = tree; 116 | return tree; 117 | } 118 | 119 | private changeTree(tree: CacheTreeFolder) { 120 | this.flushEdits(tree); 121 | } 122 | 123 | updateItem(parentPath: string, file: CacheTreeFile, name: string) { 124 | const currentFolder = this.getByPath(parentPath); 125 | if ( 126 | !currentFolder || 127 | !("children" in currentFolder) || 128 | !(name in currentFolder.children) 129 | ) 130 | return; 131 | currentFolder.children[name] = file; 132 | this.changeTree(this.inMemoryTree); 133 | } 134 | 135 | inMemoryTree: CacheTreeFolder = emptyTree(); 136 | 137 | deleteItem(parentPath: string, name: string) { 138 | const currentFolder = this.getByPath(parentPath); 139 | if ( 140 | !currentFolder || 141 | !("children" in currentFolder) || 142 | !(name in currentFolder.children) 143 | ) 144 | return; 145 | // eslint-disable-next-line @typescript-eslint/no-dynamic-delete 146 | delete currentFolder.children[name]; 147 | this.changeTree(this.inMemoryTree); 148 | } 149 | 150 | async clearAll(): Promise { 151 | return new Promise((resolve, reject) => { 152 | const request = this.getStore("readwrite").put(emptyTree()); 153 | request.onsuccess = () => { 154 | resolve(); 155 | }; 156 | request.onerror = () => { 157 | reject(request.error); 158 | }; 159 | }); 160 | } 161 | } 162 | 163 | function emptyTree(): CacheTreeFolder { 164 | return { key: "tree", children: {} }; 165 | } 166 | 167 | export interface CacheTreeFolder { 168 | key?: "tree"; 169 | children: Record; 170 | } 171 | 172 | export interface CacheTreeFile { 173 | size: number; 174 | ctime: number; 175 | mtime: number; 176 | } 177 | -------------------------------------------------------------------------------- /src/OriginalMetadataCache.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | /* eslint-disable no-prototype-builtins */ 3 | import { MetadataCache, Platform } from "obsidian"; 4 | 5 | export async function initialize(this: MetadataCache) { 6 | const uniqueFiles = Object.fromEntries( 7 | this.app.vault.getFiles().map((v) => [v.path, v]), 8 | ); 9 | await this._preload(); 10 | 11 | const fileCache = this.fileCache; 12 | const metadataCache = this.metadataCache; 13 | setInterval(this.cleanupDeletedCache.bind(this), 600000); // 10 minutes 14 | 15 | let i = 0; 16 | const batchSize = 50; 17 | const pauseTime = 100; 18 | const notice = new Notice("Starting cache updating"); 19 | for (const path in fileCache) { 20 | if (!path.endsWith(".md")) continue; 21 | if (fileCache.hasOwnProperty(path)) { 22 | const file = uniqueFiles[path]; 23 | const cachedData = fileCache[path]!; 24 | 25 | if (file) { 26 | if (metadataCache.hasOwnProperty(cachedData.hash)) { 27 | if ( 28 | Platform.isAndroidApp && 29 | file.stat.mtime !== cachedData.mtime && 30 | cachedData.mtime % 1000 === 0 && 31 | Math.floor(file.stat.mtime / 1000) * 1000 === 32 | cachedData.mtime 33 | ) { 34 | cachedData.mtime = file.stat.mtime; 35 | this.saveFileCache(path, cachedData); 36 | } 37 | 38 | if ( 39 | file.stat.mtime !== cachedData.mtime || 40 | file.stat.size !== cachedData.size 41 | ) { 42 | notice.setMessage(`Computing ${i} - change`); 43 | await this.computeFileMetadataAsync(file); 44 | i++; 45 | if (i % batchSize === 0) 46 | await new Promise((r) => setTimeout(r, pauseTime)); 47 | } else { 48 | this.linkResolverQueue?.add(file); 49 | } 50 | } else { 51 | notice.setMessage(`Computing ${i} - insert`); 52 | await this.computeFileMetadataAsync(file); 53 | i++; 54 | if (i % batchSize === 0) 55 | await new Promise((r) => setTimeout(r, pauseTime)); 56 | } 57 | } else { 58 | notice.setMessage(`Computing ${i} - delete`); 59 | this.deletePath(path); 60 | } 61 | } 62 | } 63 | 64 | for (const path in uniqueFiles) { 65 | if ( 66 | uniqueFiles.hasOwnProperty(path) && 67 | !fileCache.hasOwnProperty(path) 68 | ) { 69 | notice.setMessage(`Computing ${i} - create`); 70 | await this.computeFileMetadataAsync(uniqueFiles[path]!); 71 | i++; 72 | if (i % batchSize === 0) 73 | await new Promise((r) => setTimeout(r, pauseTime)); 74 | } 75 | } 76 | 77 | this.initialized = true; 78 | this.watchVaultChanges(); 79 | this.updateUserIgnoreFilters(); 80 | this.trigger("finished"); 81 | 82 | setTimeout(() => { 83 | this.cleanupDeletedCache(); 84 | }, 60000); // 1 minute 85 | } 86 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | import { 3 | CapacitorAdapter, 4 | FileSystemAdapter, 5 | Reference, 6 | TAbstractFile, 7 | TFile, 8 | TFolder, 9 | } from "obsidian"; 10 | 11 | import { initialize } from "./OriginalMetadataCache"; 12 | import { CacheTreeFolder, FSCache } from "./FSCache"; 13 | import PluginWithSettings from "../obsidian-reusables/src/PluginWithSettings"; 14 | import { getParentPath } from "../obsidian-reusables/src/indexFiles"; 15 | import { VirtualFSPluginSettingsTab } from "./settings"; 16 | import { CustomArrayDictImpl } from "obsidian-typings/src/obsidian/implementations/Classes/CustomArrayDictImpl"; 17 | 18 | type ReadDir = (p: string) => Promise< 19 | { 20 | name: string; 21 | type: "file" | "folder"; 22 | ctime: number; 23 | mtime: number; 24 | size: number; 25 | }[] 26 | >; 27 | 28 | export default class Main extends PluginWithSettings({}) { 29 | override async onload() { 30 | await this.initSettings(VirtualFSPluginSettingsTab); 31 | 32 | // eslint-disable-next-line @typescript-eslint/no-empty-function 33 | this.app.metadataCache.initialize = async () => {}; 34 | 35 | this.patchNodeFSAdapter(); 36 | this.patchCapacitorFSAdapter(); 37 | } 38 | 39 | private patchCapacitorFSAdapter() { 40 | this.registerPatch(CapacitorAdapter.prototype, { 41 | watchAndList(_, plugin) { 42 | return async function () { 43 | const readDir = async (p: string) => { 44 | const children = await this.fs.readdir(p); 45 | return children.map((v) => ({ 46 | name: v.name, 47 | type: 48 | v.type === "directory" 49 | ? ("folder" as const) 50 | : ("file" as const), 51 | ctime: v.ctime ?? 0, 52 | mtime: v.mtime ?? 0, 53 | size: v.size ?? 0, 54 | })); 55 | }; 56 | await plugin.lazyLoad(readDir); 57 | }; 58 | }, 59 | }); 60 | } 61 | 62 | private patchNodeFSAdapter() { 63 | this.registerPatch(FileSystemAdapter.prototype, { 64 | listAll(_, plugin) { 65 | return async function () { 66 | const readDir = async (p: string) => { 67 | const children = await this.fsPromises.readdir(p, { 68 | withFileTypes: true, 69 | }); 70 | const promises = children.map(async (v) => { 71 | const stats = await this.fsPromises.stat( 72 | p + "/" + v.name, 73 | ); 74 | return { 75 | name: v.name, 76 | type: v.isDirectory() 77 | ? ("folder" as const) 78 | : ("file" as const), 79 | ctime: stats.ctime.valueOf(), 80 | mtime: stats.mtime.valueOf(), 81 | size: stats.size, 82 | }; 83 | }); 84 | return Promise.all(promises); 85 | }; 86 | await plugin.lazyLoad(readDir); 87 | }; 88 | }, 89 | }); 90 | } 91 | 92 | fsCache: FSCache | undefined; 93 | setupListenersForCache = (fsCache: FSCache) => { 94 | const createFileInFSCache = (file: TAbstractFile) => { 95 | if (file.parent) 96 | fsCache.addItem( 97 | file.parent.path, 98 | file instanceof TFile 99 | ? { 100 | size: file.stat.size, 101 | mtime: file.stat.mtime, 102 | ctime: file.stat.ctime, 103 | } 104 | : { 105 | children: {}, 106 | }, 107 | file.name, 108 | ); 109 | }; 110 | this.registerEvent( 111 | this.app.vault.on("create", (file) => { 112 | createFileInFSCache(file); 113 | }), 114 | ); 115 | this.registerEvent( 116 | this.app.vault.on("delete", (file) => { 117 | if (file.path !== "/") 118 | fsCache.deleteItem(getParentPath(file.path)!, file.name); 119 | }), 120 | ); 121 | this.registerEvent( 122 | this.app.vault.on("rename", (file, oldPath) => { 123 | const old = fsCache.getItem(oldPath); 124 | const parentpath = 125 | oldPath.split("/").slice(0, -1).join("/") || "/"; 126 | const name = oldPath.split("/").at(-1)!; 127 | fsCache.deleteItem(parentpath, name); 128 | if (old) 129 | fsCache.addItem(file.parent?.path ?? "/", old, file.name); 130 | else createFileInFSCache(file); 131 | }), 132 | ); 133 | }; 134 | 135 | private reconcileNode = (node: Node) => { 136 | this.app.vault.adapter.files[node.path] = { 137 | type: "size" in node ? "file" : "folder", 138 | realpath: node.path, 139 | }; 140 | const existing = this.app.vault.fileMap[node.path]; 141 | if (existing) { 142 | if ("size" in node) { 143 | if (existing instanceof TFolder) 144 | this.app.vault.onChange("folder-removed", node.path); 145 | else return; 146 | } else { 147 | if (existing instanceof TFile) 148 | this.app.vault.onChange("file-removed", node.path); 149 | else if (existing instanceof TFolder) { 150 | const newByName = new Set(node.children.map((v) => v.name)); 151 | for (const child of existing.children) { 152 | const wasDeleted = !newByName.has(child.name); 153 | const isVirtual = 154 | child instanceof TFile && child.extension === "dir"; 155 | if (wasDeleted && !isVirtual) 156 | this.app.vault.onChange("file-removed", child.path); 157 | } 158 | return; 159 | } 160 | } 161 | } 162 | if ("size" in node) { 163 | this.app.vault.onChange("file-created", node.path, undefined, node); 164 | const file = this.app.vault.fileMap[node.path]; 165 | 166 | if (file instanceof TFile) 167 | this.app.metadataCache.uniqueFileLookup.add( 168 | file.name.toLowerCase(), 169 | file, 170 | ); 171 | } else this.app.vault.onChange("folder-created", node.path); 172 | }; 173 | 174 | private async lazyLoad(readdir: ReadDir) { 175 | const adapter = this.app.vault.adapter; 176 | 177 | const fsCache = new FSCache(adapter.basePath); 178 | this.fsCache = fsCache; 179 | console.time("reading cache"); 180 | const wasInitialised = await fsCache.init(); 181 | const tree = await fsCache.getTree(); 182 | console.timeEnd("reading cache"); 183 | const wasEmpty = 184 | wasInitialised || Object.keys(tree.children).length === 0; 185 | 186 | const otherHandlers = this.app.vault._; 187 | this.app.vault._ = {}; 188 | 189 | console.time("applying cache"); 190 | this.visitCachedNodes(tree, this.reconcileNode, "/"); 191 | console.timeEnd("applying cache"); 192 | this.app.vault._ = otherHandlers; 193 | 194 | this.setupListenersForCache(fsCache); 195 | const reconcilePromise = this.visitRealNodes( 196 | "/", 197 | this.reconcileNode, 198 | readdir, 199 | wasEmpty ? undefined : 10, 200 | ); 201 | const updateMetadataCache = async () => { 202 | console.time("metadata"); 203 | await initialize.bind(this.app.metadataCache)(); 204 | console.timeEnd("metadata"); 205 | }; 206 | 207 | const updateFile = (file: TFile) => { 208 | for (const [target] of file.links ?? []) { 209 | target.backlinks?.delete(file); 210 | } 211 | const cache = this.app.metadataCache.getFileCache(file); 212 | if (cache) { 213 | file.links ??= new Map(); 214 | for (const link of [ 215 | ...(cache.links ?? []), 216 | ...(cache.frontmatterLinks ?? []), 217 | ]) { 218 | const target = this.app.metadataCache.getFirstLinkpathDest( 219 | link.link, 220 | file.path, 221 | ); 222 | if (!target) continue; 223 | target.backlinks ??= new Map(); 224 | const refs = target.backlinks.get(file) ?? []; 225 | refs.push(link); 226 | target.backlinks.set(file, refs); 227 | 228 | const outRefs = file.links.get(target) ?? []; 229 | outRefs.push(link); 230 | file.links.set(target, outRefs); 231 | } 232 | } 233 | }; 234 | const updateBacklinks = async () => { 235 | const notice = new Notice("Starting filling backlinks", 100000); 236 | console.time("backlinks"); 237 | const files = this.app.vault.getFiles(); 238 | for (let i = 0; i < files.length; i++) { 239 | const file = files[i]; 240 | if (file) updateFile(file); 241 | if (i % 100 === 0) await new Promise((r) => setTimeout(r, 1)); 242 | } 243 | console.timeEnd("backlinks"); 244 | notice.setMessage("Finished filling backlinks"); 245 | setTimeout(() => { 246 | notice.hide(); 247 | }, 2000); 248 | }; 249 | const bindFileWatchers = 250 | adapter instanceof FileSystemAdapter 251 | ? async () => { 252 | const notice = new Notice( 253 | "Starting setting watches", 254 | 100000, 255 | ); 256 | console.time("watches"); 257 | const files = Object.keys(adapter.files); 258 | for (let i = 0; i < files.length; i++) { 259 | const file = files[i]!; 260 | await adapter.startWatchPath(file); 261 | if (i % 20 === 0) 262 | await new Promise((r) => setTimeout(r, 20)); 263 | } 264 | console.timeEnd("watches"); 265 | notice.setMessage("Finished setting watches"); 266 | setTimeout(() => { 267 | notice.hide(); 268 | }, 2000); 269 | } 270 | : async () => { 271 | /* file watchers are not supported by the adapter */ 272 | }; 273 | if (wasEmpty) { 274 | console.time("filling cache"); 275 | await reconcilePromise; 276 | console.timeEnd("filling cache"); 277 | void updateMetadataCache() 278 | .then(updateBacklinks) 279 | .then(bindFileWatchers); 280 | } else { 281 | console.time("updating cache"); 282 | void updateBacklinks(); 283 | void reconcilePromise 284 | .then(() => { 285 | console.timeEnd("updating cache"); 286 | }) 287 | .then(updateMetadataCache) 288 | // .then(updateBacklinks) 289 | .then(bindFileWatchers); 290 | } 291 | 292 | this.app.metadataCache.on("resolve", (file) => { 293 | updateFile(file); 294 | }); 295 | this.app.metadataCache.on("changed", (file) => { 296 | updateFile(file); 297 | }); 298 | this.app.vault.on("delete", (file) => { 299 | if (file instanceof TFile) 300 | for (const [target] of file.links ?? []) { 301 | target.backlinks?.delete(file); 302 | } 303 | }); 304 | 305 | this.registerPatch(this.app.metadataCache, { 306 | getBacklinksForFile() { 307 | return (file) => { 308 | const dict = new CustomArrayDictImpl(); 309 | dict.data = new Map( 310 | [...(file.backlinks?.entries() ?? [])].map(([k, v]) => [ 311 | k.path, 312 | v, 313 | ]), 314 | ); 315 | return dict; 316 | }; 317 | }, 318 | }); 319 | 320 | return fsCache; 321 | } 322 | private visitCachedNodes( 323 | tree: CacheTreeFolder, 324 | visitor: (node: Node) => void, 325 | path: string, 326 | ) { 327 | visitor({ 328 | type: "folder", 329 | path, 330 | children: Object.entries(tree.children).map(([name, v]) => 331 | "children" in v 332 | ? { type: "folder", name } 333 | : { type: "file", name }, 334 | ), 335 | }); 336 | for (const [name, child] of Object.entries(tree.children)) { 337 | const nextPath = path === "/" ? name : `${path}/${name}`; 338 | if ("children" in child) { 339 | this.visitCachedNodes(child, visitor, nextPath); 340 | } else { 341 | visitor({ 342 | type: "file", 343 | path: nextPath, 344 | ctime: child.ctime, 345 | mtime: child.mtime, 346 | size: child.size, 347 | }); 348 | } 349 | } 350 | } 351 | 352 | async visitRealNodes( 353 | p: string, 354 | visitor: (node: Node) => void, 355 | readdir: ReadDir, 356 | timeout?: number, 357 | ) { 358 | const base = this.app.vault.adapter.basePath; 359 | const fullPath = p === "/" ? base : base + "/" + p; 360 | const children = await readdir(fullPath); 361 | if (timeout !== undefined) 362 | await new Promise((r) => setTimeout(r, timeout)); 363 | 364 | visitor({ type: "folder", path: p, children }); 365 | for (const c of children) { 366 | const nextPath = p === "/" ? c.name : `${p}/${c.name}`; 367 | if (c.name.startsWith(".")) continue; 368 | if (c.type === "folder") { 369 | await this.visitRealNodes(nextPath, visitor, readdir); 370 | } else { 371 | visitor({ 372 | type: "file", 373 | path: nextPath, 374 | ctime: c.ctime, 375 | mtime: c.mtime, 376 | size: c.size, 377 | }); 378 | } 379 | } 380 | } 381 | } 382 | 383 | declare module "obsidian" { 384 | interface TFile extends TAbstractFile { 385 | backlinks?: Map; 386 | links?: Map; 387 | } 388 | } 389 | type Node = 390 | | { 391 | type: "folder"; 392 | path: string; 393 | children: { 394 | name: string; 395 | type: "file" | "folder"; 396 | ctime?: number; 397 | mtime?: number; 398 | size?: number; 399 | }[]; 400 | } 401 | | { 402 | type: "file"; 403 | path: string; 404 | mtime: number; 405 | ctime: number; 406 | size: number; 407 | }; 408 | -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | import { PluginSettingTab, App, Setting } from "obsidian"; 2 | import Main from "./main"; 3 | 4 | export class VirtualFSPluginSettingsTab extends PluginSettingTab { 5 | constructor( 6 | app: App, 7 | override plugin: Main, 8 | ) { 9 | super(app, plugin); 10 | this.plugin = plugin; 11 | } 12 | 13 | display() { 14 | const { containerEl } = this; 15 | containerEl.empty(); 16 | 17 | new Setting(containerEl) 18 | .setName("Cache management") 19 | .setDesc("Manage vault cache") 20 | .addButton((button) => 21 | button 22 | .setButtonText("Clear cache") 23 | .setCta() 24 | .onClick(() => { 25 | void this.plugin.fsCache?.clearAll().then(() => { 26 | new Notice("Cleared cache!"); 27 | }); 28 | }), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/strictest/tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "inlineSourceMap": true, 6 | "inlineSources": true, 7 | "module": "ESNext", 8 | "target": "ES6", 9 | "allowJs": true, 10 | "moduleResolution": "node", 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "allowImportingTsExtensions": true, 14 | "lib": ["DOM", "ES5", "ES6", "ES7"] 15 | }, 16 | "include": ["src/**/*.ts", "obsidian-typings/src/**/*.d.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | 3 | const targetVersion = process.env.npm_package_version; 4 | 5 | // read minAppVersion from manifest.json and bump version to target version 6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); 7 | const { minAppVersion } = manifest; 8 | manifest.version = targetVersion; 9 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); 10 | 11 | // update versions.json with target version and minAppVersion from manifest.json 12 | let versions = JSON.parse(readFileSync("versions.json", "utf8")); 13 | versions[targetVersion] = minAppVersion; 14 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); 15 | --------------------------------------------------------------------------------