├── .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 |
--------------------------------------------------------------------------------