├── .changeset ├── README.md └── config.json ├── .eslintrc.cjs ├── .github └── workflows │ ├── main.yml │ └── release.yml ├── .gitignore ├── .npmignore ├── .vscode ├── extension.json └── settings.json ├── CHANGELOG.md ├── LICENSE_APACHE-2.0 ├── LICENSE_MIT ├── README.md ├── dprint.json ├── example ├── .gitignore ├── .prettierrc.json ├── .vscode │ └── extensions.json ├── README.md ├── index.html ├── package.json ├── pnpm-lock.yaml ├── public │ ├── svelte.svg │ ├── tauri.svg │ └── vite.svg ├── src-tauri │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ ├── capabilities │ │ └── default.json │ ├── gen │ │ └── schemas │ │ │ ├── acl-manifests.json │ │ │ ├── capabilities.json │ │ │ ├── desktop-schema.json │ │ │ └── windows-schema.json │ ├── icons │ │ ├── 128x128.png │ │ ├── 128x128@2x.png │ │ ├── 32x32.png │ │ ├── Square107x107Logo.png │ │ ├── Square142x142Logo.png │ │ ├── Square150x150Logo.png │ │ ├── Square284x284Logo.png │ │ ├── Square30x30Logo.png │ │ ├── Square310x310Logo.png │ │ ├── Square44x44Logo.png │ │ ├── Square71x71Logo.png │ │ ├── Square89x89Logo.png │ │ ├── StoreLogo.png │ │ ├── icon.icns │ │ ├── icon.ico │ │ └── icon.png │ ├── src │ │ └── main.rs │ └── tauri.conf.json ├── src │ ├── App.svelte │ ├── lib │ │ ├── bindings.ts │ │ └── ipc.ts │ ├── main.ts │ ├── styles.css │ └── vite-env.d.ts ├── svelte.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── package.json ├── pnpm-lock.yaml ├── src └── index.ts ├── taurpc ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-MIT ├── src │ ├── export.rs │ └── lib.rs └── taurpc-macros │ ├── Cargo.toml │ ├── README.md │ └── src │ ├── args.rs │ ├── attrs.rs │ ├── generator.rs │ ├── lib.rs │ └── proc.rs └── tsconfig.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": [ 4 | "@changesets/changelog-github", 5 | { 6 | "repo": "MatsDK/TauRPC" 7 | } 8 | ], 9 | "commit": false, 10 | "fixed": [], 11 | "linked": [], 12 | "access": "public", 13 | "baseBranch": "main", 14 | "updateInternalDependencies": "patch", 15 | "ignore": [] 16 | } 17 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("eslint").Linter.Config} */ 2 | const config = { 3 | parser: '@typescript-eslint/parser', 4 | env: { 5 | node: true, 6 | browser: true, 7 | }, 8 | extends: [ 9 | 'plugin:@typescript-eslint/recommended', 10 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 11 | 'eslint:recommended', 12 | // 'plugin:dprint/recommended', 13 | ], 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | tsconfigRootDir: __dirname, 17 | project: [ 18 | './tsconfig.json', 19 | './example/tsconfig.json', 20 | ], 21 | }, 22 | rules: { 23 | 'no-unused-vars': 'off', 24 | '@typescript-eslint/no-unused-vars': [ 25 | 'warn', // or "error" 26 | { 27 | 'argsIgnorePattern': '^_', 28 | 'varsIgnorePattern': '^_', 29 | 'caughtErrorsIgnorePattern': '^_', 30 | }, 31 | ], 32 | }, 33 | plugins: [ 34 | '@typescript-eslint', 35 | ], 36 | ignorePatterns: [ 37 | 'node_modules', 38 | 'dist', 39 | 'taurpc', 40 | 'target', 41 | 'example', 42 | ], 43 | } 44 | 45 | module.exports = config 46 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: ["*"] 6 | push: 7 | branches: ["main"] 8 | merge_group: 9 | 10 | jobs: 11 | build-lint: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout repo 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup pnpm 19 | uses: pnpm/action-setup@v4 20 | 21 | - name: Setup Node 18 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: 18 25 | 26 | - name: Get pnpm store directory 27 | id: pnpm-cache 28 | run: | 29 | echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT 30 | 31 | - name: Setup pnpm cache 32 | uses: actions/cache@v3 33 | with: 34 | path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} 35 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 36 | restore-keys: | 37 | ${{ runner.os }}-pnpm-store- 38 | 39 | - name: Install deps (with cache) 40 | run: pnpm install 41 | 42 | - name: Build 43 | run: pnpm build 44 | 45 | - name: Lint 46 | run: pnpm lint 47 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | release: 9 | name: Release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Use PNPM 18 | uses: pnpm/action-setup@v4 19 | 20 | - name: Setup Node.js 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: 18 24 | 25 | - name: Get pnpm store directory 26 | id: pnpm-cache 27 | run: | 28 | echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT 29 | 30 | - name: Setup pnpm cache 31 | uses: actions/cache@v3 32 | with: 33 | path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} 34 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 35 | restore-keys: | 36 | ${{ runner.os }}-pnpm-store- 37 | 38 | - name: Install deps (with cache) 39 | run: pnpm install 40 | 41 | - name: Build 42 | run: pnpm build 43 | 44 | # Using custom token `MY_GITHUB_TOKEN` with more access to avoid rate limiting 45 | - name: Create Release 46 | id: changeset 47 | uses: changesets/action@v1.4.4 48 | with: 49 | commit: "chore(release): 📦 version packages" 50 | title: "chore(release): 📦 version packages" 51 | publish: npx changeset publish 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | # GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }} 55 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 56 | 57 | # Changeset has some issues with pnpm so we sync it up manually 58 | - name: Sync lockfile if necessary 59 | if: steps.changeset.outputs.hasChangesets == 'true' 60 | run: | 61 | git checkout changeset-release/main 62 | pnpm install --no-frozen-lockfile 63 | git add . 64 | git commit -m "chore(release): 📦 sync lockfile" 65 | git push origin changeset-release/main 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | taurpc 3 | -------------------------------------------------------------------------------- /.vscode/extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "dprint.dprint" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "dprint.dprint", 3 | "dprint.path": "node_modules/dprint/dprint", 4 | "typescript.preferences.importModuleSpecifierEnding": "js", 5 | "typescript.tsdk": "node_modules\\typescript\\lib", 6 | "rust-analyzer.linkedProjects": [ 7 | "taurpc/Cargo.toml", 8 | "example/src-tauri/Cargo.toml" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # taurpc 2 | 3 | ## 1.8.1 4 | 5 | ### Patch Changes 6 | 7 | - [`92b170e`](https://github.com/MatsDK/TauRPC/commit/92b170ed44f062b1da3989f1ce40cb315dcc0446) Thanks [@MatsDK](https://github.com/MatsDK)! - Actually return unlisten function for events 8 | 9 | ## 1.8.0 10 | 11 | ### Minor Changes 12 | 13 | - [`04af2f3`](https://github.com/MatsDK/TauRPC/commit/04af2f3565571777f6d76f9fb3d71538ec574313) Thanks [@MatsDK](https://github.com/MatsDK)! 14 | - Support Tauri channels [#34](https://github.com/MatsDK/TauRPC/issues/34) 15 | - Better error handling in exporter [#43](https://github.com/MatsDK/TauRPC/issues/43) 16 | - Show correct names for parameters on the frontend types [#37](https://github.com/MatsDK/TauRPC/issues/37) 17 | 18 | ## 1.7.0 19 | 20 | ### Minor Changes 21 | 22 | - [`3df869f`](https://github.com/MatsDK/TauRPC/commit/3df869fc85f7f1fcc41525207e504558b81bedee) Thanks [@MatsDK](https://github.com/MatsDK)! - Fix unnecessary await for event handlers [#38](https://github.com/MatsDK/TauRPC/issues/38). 23 | 24 | ## 1.6.0 25 | 26 | ### Minor Changes 27 | 28 | - [`a2b457a`](https://github.com/MatsDK/TauRPC/commit/a2b457a0e4531fbd31ea5d5d6bb834e247375fec) Thanks [@MatsDK](https://github.com/MatsDK)! - support tauri@2.0.0 29 | 30 | ## 1.4.3 31 | 32 | ### Patch Changes 33 | 34 | - [`2ffad75`](https://github.com/MatsDK/TauRPC/commit/2ffad7527a55b51fc926d90515331053777aa37a) Thanks [@MatsDK](https://github.com/MatsDK)! 35 | 36 | - Allow doc comments on IPC types - [#21](https://github.com/MatsDK/TauRPC/issues/21) 37 | - Allow users to declare a router without root procedures - [#22](https://github.com/MatsDK/TauRPC/issues/22) 38 | 39 | ## 1.4.2 40 | 41 | ### Patch Changes 42 | 43 | - [`0a87d07`](https://github.com/MatsDK/TauRPC/commit/0a87d0778c9b64af1e21e0d9ca5bcb8a9f746ff5) Thanks [@MatsDK](https://github.com/MatsDK)! - Fix issue when the only argument is of type Vec or a tuple for events. 44 | 45 | ## 1.4.1 46 | 47 | ### Patch Changes 48 | 49 | - [`4c0b1b4`](https://github.com/MatsDK/TauRPC/commit/4c0b1b44ae83fdbbcb154d1f32904181a28a6419) Thanks [@MatsDK](https://github.com/MatsDK)! - 50 | - Fix [issue](https://github.com/MatsDK/TauRPC/issues/14) when the only argument is of type `Vec` or a tuple. 51 | - Set default export to `../bindings.ts`. 52 | - Directly generate args_map with types instead of using `TauRpc__setup`. 53 | 54 | ## 1.4.0 55 | 56 | ### Minor Changes 57 | 58 | - [`8df57cf`](https://github.com/MatsDK/TauRPC/commit/8df57cf221f8cab0a7de6c39f54eee9b095ad2d3) Thanks [@MatsDK](https://github.com/MatsDK)! - Allow users to create nested commands that can be called with a proxy-like ts client 59 | 60 | ## 1.3.1 61 | 62 | ### Patch Changes 63 | 64 | - [`31690ca`](https://github.com/MatsDK/TauRPC/commit/31690cadacbee837b73fcf471955936296f67431) Thanks [@MatsDK](https://github.com/MatsDK)! - event attribute so you are not forced to implement a resolver for them 65 | 66 | ## 1.3.0 67 | 68 | ### Minor Changes 69 | 70 | - [`8a7b495`](https://github.com/MatsDK/TauRPC/commit/8a7b495f6c96b8ef4f8fc706e4b51c1f2793ebc5) Thanks [@MatsDK](https://github.com/MatsDK)! 71 | - Switch from `ts_rs` to `specta` for the type-generation. 72 | - Allow to specify `export_to` attribute on procedures for exporting the generated types. 73 | - Windows enum for sending scoped events. 74 | - Common client for both invoking commands and listening to events. 75 | 76 | ## 1.2.4 77 | 78 | ### Patch Changes 79 | 80 | - [`2bae0ca`](https://github.com/MatsDK/TauRPC/commit/2bae0ca9c1eee7f36d2ab2bcbd6773792babd475) Thanks [@MatsDK](https://github.com/MatsDK)! - alias/skip method attributes 81 | 82 | ## 1.2.3 83 | 84 | ### Patch Changes 85 | 86 | - [`209358c`](https://github.com/MatsDK/TauRPC/commit/209358c2084e6a77a3e34e5a20b9a8614361720c) Thanks [@MatsDK](https://github.com/MatsDK)! - rename event trigger, event scope 87 | 88 | - [`3c8fee9`](https://github.com/MatsDK/TauRPC/commit/3c8fee9af6571f420ec121c33adfc91382592681) Thanks [@MatsDK](https://github.com/MatsDK)! - trigger events on client side, with types 89 | 90 | ## 1.2.2 91 | 92 | ### Patch Changes 93 | 94 | - [`0424f61`](https://github.com/MatsDK/TauRPC/commit/0424f611f812d8ccfc9055cbddbceee7a5fef023) Thanks [@MatsDK](https://github.com/MatsDK)! - Custom error handling using Result types 95 | 96 | ## 1.2.1 97 | 98 | ### Patch Changes 99 | 100 | - [`3c98a2c`](https://github.com/MatsDK/TauRPC/commit/3c98a2cb0bf07fb3100a927d0aa2f84d76f8aea2) Thanks [@MatsDK](https://github.com/MatsDK)! - make procedures async 101 | 102 | - [`054ed4b`](https://github.com/MatsDK/TauRPC/commit/054ed4b22afb25bc3d5b178f82485af4ec313c32) Thanks [@MatsDK](https://github.com/MatsDK)! - support for async methods 103 | 104 | ## 1.2.0 105 | 106 | ### Minor Changes 107 | 108 | - [`0fc1bf0`](https://github.com/MatsDK/TauRPC/commit/0fc1bf07d1feb0e6520dafc0af23199bcb1dccc6) Thanks [@MatsDK](https://github.com/MatsDK)! - state/window/app_handle 109 | 110 | - [`60deedf`](https://github.com/MatsDK/TauRPC/commit/60deedfa91a7d04f654e1d52677d5e543b365788) Thanks [@MatsDK](https://github.com/MatsDK)! - use state/window/app_handle in commands 111 | 112 | ## 1.1.0 113 | 114 | ### Minor Changes 115 | 116 | - [`0896862`](https://github.com/MatsDK/TauRPC/commit/089686280c2192a104467a0976b107b520fb8a8b) Thanks [@MatsDK](https://github.com/MatsDK)! - add types for outputs 117 | -------------------------------------------------------------------------------- /LICENSE_APACHE-2.0: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /LICENSE_MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Present Tauri Apps Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TauRPC 2 | 3 | [![](https://img.shields.io/npm/v/taurpc)](https://www.npmjs.com/package/taurpc) [![](https://img.shields.io/crates/v/taurpc)](https://crates.io/crates/taurpc) [![](https://img.shields.io/docsrs/taurpc)](https://docs.rs/taurpc/) ![](https://img.shields.io/crates/l/taurpc) 4 | 5 | This package is a Tauri extension to give you a fully-typed IPC layer for [Tauri commands](https://v2.tauri.app/develop/calling-rust/#commands) and [events](https://v2.tauri.app/develop/calling-rust/#event-system). 6 | 7 | The TS types corresponding to your pre-defined Rust backend API are generated on runtime, after which they can be used to call the backend from your TypeScript frontend framework of choice. This crate provides typesafe bidirectional IPC communication between the Rust backend and TypeScript frontend. 8 | [Specta](https://github.com/oscartbeaumont/specta) is used under the hood for the type-generation. The trait-based API structure was inspired by [tarpc](https://github.com/google/tarpc). 9 | 10 | # Usage🔧 11 | 12 | First, add the following crates to your `Cargo.toml`: 13 | 14 | ```toml 15 | # src-tauri/Cargo.toml 16 | 17 | [dependencies] 18 | taurpc = "0.5.0" 19 | 20 | specta = { version = "=2.0.0-rc.22", features = ["derive"] } 21 | # specta-typescript = "0.0.9" 22 | tokio = { version = "1", features = ["full"] } 23 | ``` 24 | 25 | Then, declare and implement your IPC methods and resolvers. If you want to use your API for Tauri's events, you don't have to implement the resolvers, go to [Calling the frontend](https://github.com/MatsDK/TauRPC/#calling-the-frontend) 26 | 27 | ```rust 28 | // src-tauri/src/main.rs 29 | 30 | #[taurpc::procedures] 31 | trait Api { 32 | async fn hello_world(); 33 | } 34 | 35 | #[derive(Clone)] 36 | struct ApiImpl; 37 | 38 | #[taurpc::resolvers] 39 | impl Api for ApiImpl { 40 | async fn hello_world(self) { 41 | println!("Hello world"); 42 | } 43 | } 44 | 45 | #[tokio::main] 46 | async fn main() { 47 | tauri::Builder::default() 48 | .plugin(tauri_plugin_shell::init()) 49 | .invoke_handler(taurpc::create_ipc_handler(ApiImpl.into_handler())) 50 | .run(tauri::generate_context!()) 51 | .expect("error while running tauri application"); 52 | } 53 | ``` 54 | 55 | The `#[taurpc::procedures]` trait will generate everything necessary for handling calls and the type-generation. Now, you should run `pnpm tauri dev` to generate and export the TS types. 56 | The types will by default be exported to `bindings.ts` in the root of your project, but you can specify an export path by doing this `#[taurpc::procedures(export_to = "../src/types.ts")]`. 57 | 58 | Then on the frontend install the taurpc package. 59 | 60 | ```bash 61 | pnpm install taurpc 62 | ``` 63 | 64 | Now on the frontend you import the generated types, if you specified the `export_to` attribute on your procedures you should import your from there. 65 | With these types a typesafe proxy is generated that you can use to invoke commands and listen for events. 66 | 67 | ```typescript 68 | import { createTauRPCProxy } from '../bindings.ts' 69 | 70 | const taurpc = createTauRPCProxy() 71 | await taurpc.hello_world() 72 | ``` 73 | 74 | The types for taurpc are generated once you start your application, run `pnpm tauri dev`. If the types are not picked up by the LSP, you may have to restart typescript to reload the types. 75 | 76 | You can find a complete example (using Svelte) [here](https://github.com/MatsDK/TauRPC/tree/main/example). 77 | 78 | # Using structs 79 | 80 | If you want to use structs for the inputs/outputs of procedures, you should always add `#[taurpc::ipc_type]` to make sure the coresponding ts types are generated. This make will derive serde `Serialize` and `Deserialize`, `Clone` and `specta::Type`. 81 | 82 | ```rust 83 | #[taurpc::ipc_type] 84 | // #[derive(serde::Serialize, serde::Deserialize, specta::Type, Clone)] 85 | struct User { 86 | user_id: u32, 87 | first_name: String, 88 | last_name: String, 89 | } 90 | 91 | #[taurpc::procedures] 92 | trait Api { 93 | async fn get_user() -> User; 94 | } 95 | ``` 96 | 97 | # Accessing managed state 98 | 99 | To share some state between procedures, you can add fields on the API implementation struct. If the state requires to be mutable, you need to use a container that enables interior mutability, like a [Mutex](https://doc.rust-lang.org/std/sync/struct.Mutex.html). 100 | 101 | You can use the `window`, `app_handle` and `webview_window` arguments just like with Tauri's commands. [Tauri docs](https://v2.tauri.app/develop/calling-rust/#accessing-the-webviewwindow-in-commands) 102 | 103 | ```rust 104 | // src-tauri/src/main.rs 105 | 106 | use std::sync::Arc; 107 | use tokio::sync::Mutex; 108 | use tauri::{Manager, Runtime, State, Window}; 109 | 110 | type MyState = Arc>; 111 | 112 | #[taurpc::procedures] 113 | trait Api { 114 | async fn with_state(); 115 | 116 | async fn with_window(window: Window); 117 | } 118 | 119 | #[derive(Clone)] 120 | struct ApiImpl { 121 | state: MyState 122 | }; 123 | 124 | #[taurpc::resolvers] 125 | impl Api for ApiImpl { 126 | async fn with_state(self) { 127 | // ... 128 | // let state = self.state.lock().await; 129 | // ... 130 | } 131 | 132 | async fn with_window(self, window: Window) { 133 | // ... 134 | } 135 | } 136 | 137 | #[tokio::main] 138 | async fn main() { 139 | tauri::Builder::default() 140 | .invoke_handler(taurpc::create_ipc_handler( 141 | ApiImpl { 142 | state: Arc::new(Mutex::new("state".to_string())), 143 | } 144 | .into_handler(), 145 | )) 146 | .run(tauri::generate_context!()) 147 | .expect("error while running tauri application"); 148 | } 149 | ``` 150 | 151 | # Custom error handling 152 | 153 | You can return a `Result` to return an error if the procedure fails. This is will reject the promise on the frontend and throw an error. 154 | If you're working with error types from Rust's std library, they will probably not implement `serde::Serialize` which is required for anything that is returned in the procedure. 155 | In simple scenarios you can use `map_err` to convert these errors to `String`s. For more complex scenarios, you can create your own error type that implements `serde::Serialize`. 156 | You can find an example using [thiserror](https://github.com/dtolnay/thiserror) [here](https://github.com/MatsDK/TauRPC/blob/main/example/src-tauri/src/main.rs). 157 | You can also find more information about this in the [Tauri guides](https://v2.tauri.app/develop/calling-rust/#error-handling). 158 | 159 | # Extra options for procedures 160 | 161 | Inside your procedures trait you can add attributes to the defined methods. This can be used to ignore or rename a method. Renaming will change the name of the procedure on the frontend. 162 | 163 | ```rust 164 | #[taurpc::procedures] 165 | trait Api { 166 | // #[taurpc(skip)] 167 | #[taurpc(alias = "_hello_world_")] 168 | async fn hello_world(); 169 | } 170 | ``` 171 | 172 | # Routing 173 | 174 | It is possible to define all your commands and events inside a single procedures trait, but this can quickly get cluttered. By using the `Router` struct you can create nested commands and events, 175 | that you can call using a proxy TypeScript client. 176 | 177 | The path of the procedures trait is set by using the `path` attribute on `#[taurpc::procedures(path = "")]`, then you can create an empty router and use the `merge` method to add handlers to the router. 178 | You can only have 1 trait without a path specified, this will be the root. Finally instead of using `taurpc::create_ipc_handler()`, you should just call `into_handler()` on the router. 179 | 180 | ```rust 181 | // Root procedures 182 | #[taurpc::procedures] 183 | trait Api { 184 | async fn hello_world(); 185 | } 186 | 187 | #[derive(Clone)] 188 | struct ApiImpl; 189 | 190 | #[taurpc::resolvers] 191 | impl Api for ApiImpl { 192 | async fn hello_world(self) { 193 | println!("Hello world"); 194 | } 195 | } 196 | 197 | // Nested procedures, you can also do this (path = "api.events.users") 198 | #[taurpc::procedures(path = "events")] 199 | trait Events { 200 | #[taurpc(event)] 201 | async fn event(); 202 | } 203 | 204 | #[derive(Clone)] 205 | struct EventsImpl; 206 | 207 | #[taurpc::resolvers] 208 | impl Events for EventsImpl {} 209 | 210 | #[tokio::main] 211 | async fn main() { 212 | let router = Router::new() 213 | .merge(ApiImpl.into_handler()) 214 | .merge(EventsImpl.into_handler()); 215 | 216 | tauri::Builder::default() 217 | .invoke_handler(router.into_handler()) 218 | .run(tauri::generate_context!()) 219 | .expect("error while running tauri application"); 220 | } 221 | ``` 222 | 223 | Now on the frontend you can use the proxy client. 224 | 225 | ```typescript 226 | // Call `hello_world` on the root layer 227 | await taurpc.hello_world() 228 | 229 | // Listen for `event` on the `events` layer 230 | const unlisten = await taurpc.events.event.on(() => { 231 | console.log('Hello World!') 232 | }) 233 | ``` 234 | 235 | # Typescript export configuration 236 | 237 | You can specify a `Specta` typescript export configuration on the `Router`. These options will overwrite `Specta`'s defaults. Make sure to install the latest version of `specta_typescript`. 238 | All available options can be found in [specta_typescript's docs](https://docs.rs/specta-typescript/latest/specta_typescript/struct.Typescript.html). 239 | 240 | ```rust 241 | let router = Router::new() 242 | .export_config( 243 | specta_typescript::Typescript::default() 244 | .header("// My header\n") 245 | .bigint(specta_typescript::BigIntExportBehavior::String), 246 | // Make sure you have the specified formatter installed on your system. 247 | .formatter(specta_typescript::formatter::prettier) 248 | ) 249 | .merge(ApiImpl.into_handler()) 250 | .merge(EventsImpl.into_handler()); 251 | ``` 252 | 253 | # Calling the frontend 254 | 255 | Trigger [events](https://v2.tauri.app/develop/calling-rust/#event-system) on your TypeScript frontend from your Rust backend with a fully-typed experience. 256 | The `#[taurpc::procedures]` macro also generates a struct that you can use to trigger the events, this means you can define the event types the same way you define the procedures. 257 | 258 | First start by declaring the API structure, by default the event trigger struct will be identified by `TauRpc{trait_ident}EventTrigger`. If you want to change this, you can add an attribute to do this, `#[taurpc::procedures(event_trigger = ApiEventTrigger)]`. 259 | For more details you can look at the [example](https://github.com/MatsDK/TauRPC/blob/main/example/src-tauri/src/main.rs). 260 | 261 | You should add the `#[taurpc(event)]` attribute to your events. If you do this, you will not have to implement the corresponding resolver. 262 | 263 | ```rust 264 | // src-tauri/src/main.rs 265 | 266 | #[taurpc::procedures(event_trigger = ApiEventTrigger)] 267 | trait Api { 268 | #[taurpc(event)] 269 | async fn hello_world(); 270 | } 271 | 272 | #[derive(Clone)] 273 | struct ApiImpl; 274 | 275 | #[taurpc::resolvers] 276 | impl Api for ApiImpl {} 277 | 278 | #[tokio::main] 279 | async fn main() { 280 | tauri::Builder::default() 281 | .invoke_handler(taurpc::create_ipc_handler(ApiImpl.into_handler())) 282 | .setup(|app| { 283 | let trigger = ApiEventTrigger::new(app.handle()); 284 | trigger.hello_world()?; 285 | 286 | Ok(()) 287 | }) 288 | .run(tauri::generate_context!()) 289 | .expect("error while running tauri application"); 290 | } 291 | ``` 292 | 293 | Then, on the frontend you can listen for the events with types: 294 | 295 | ```typescript 296 | const unlisten = await taurpc.hello_world.on(() => { 297 | console.log('Hello World!') 298 | }) 299 | 300 | // Run this inside a cleanup function, for example within useEffect in React and onDestroy in Svelte 301 | unlisten() 302 | ``` 303 | 304 | ## Sending an event to a specific window 305 | 306 | By default, events are emitted to all windows. If you want to send an event to a specific window by label, you can do the following: 307 | 308 | ```rust 309 | use taurpc::Windows; 310 | 311 | trigger.send_to(Windows::One("main".to_string())).hello_world()?; 312 | // Options: 313 | // - Windows::All (default) 314 | // - Windows::One(String) 315 | // - Windows::N(Vec) 316 | ``` 317 | 318 | # Using channels 319 | 320 | TauRPC will also generate types if you are using [Tauri Channels](https://v2.tauri.app/develop/calling-frontend/#channels). 321 | On the frontend you will be able to pass a typed callback function to your command. 322 | 323 | ```rust 324 | #[taurpc::ipc_type] 325 | struct Update { 326 | progress: u8, 327 | } 328 | 329 | #[taurpc::procedures] 330 | trait Api { 331 | async fn update(on_event: Channel); 332 | } 333 | 334 | #[derive(Clone)] 335 | struct ApiImpl; 336 | 337 | #[taurpc::resolvers] 338 | impl Api for ApiImpl { 339 | async fn update(self, on_event: Channel) { 340 | for progress in [15, 20, 35, 50, 90] { 341 | on_event.send(Update { progress }).unwrap(); 342 | } 343 | } 344 | } 345 | ``` 346 | 347 | Calling the command: 348 | 349 | ```typescript 350 | let taurpc = createTauRPCProxy() 351 | await taurpc.update((update) => { 352 | console.log(update.progress) 353 | }) 354 | ``` 355 | 356 | # Features 357 | 358 | - [x] Basic inputs 359 | - [x] Struct inputs 360 | - [x] Sharing state 361 | - [ ] Use Tauri's managed state? 362 | - [x] Renaming methods 363 | - [x] Nested routes 364 | - [x] Merging routers 365 | - [x] Custom error handling 366 | - [x] Typed outputs 367 | - [x] Async methods - [async traits👀](https://blog.rust-lang.org/inside-rust/2023/05/03/stabilizing-async-fn-in-trait.html) 368 | - [ ] Allow sync methods 369 | - [x] Calling the frontend 370 | - [x] Renaming event trigger struct 371 | - [x] Send event to specific window 372 | - [ ] React/Svelte handlers 373 | -------------------------------------------------------------------------------- /dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "incremental": true, 3 | "lineWidth": 80, 4 | "typescript": { 5 | "quoteStyle": "preferSingle", 6 | "semiColons": "asi" 7 | }, 8 | "exec": { 9 | "associations": "**/*.{rs}", 10 | "rustfmt": "rustfmt --edition 2021", 11 | "rustfmt.associations": "**/*.rs" 12 | }, 13 | "includes": [ 14 | "**/*.{ts,tsx,js,jsx,cjs,mjs,json,json5,md,yaml,rs}" 15 | ], 16 | "excludes": [ 17 | "**/node_modules", 18 | "pnpm-lock.yaml", 19 | "dist", 20 | "**/target", 21 | "**/gen" 22 | ], 23 | "plugins": [ 24 | "https://plugins.dprint.dev/typescript-0.78.0.wasm", 25 | "https://plugins.dprint.dev/json-0.16.0.wasm", 26 | "https://plugins.dprint.dev/markdown-0.14.1.wasm", 27 | "https://plugins.dprint.dev/exec-0.3.5.json@d687dda57be0fe9a0088ccdaefa5147649ff24127d8b3ea227536c68ee7abeab" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /example/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "quoteProps": "consistent" 5 | } 6 | -------------------------------------------------------------------------------- /example/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "svelte.svelte-vscode", 4 | "tauri-apps.tauri-vscode", 5 | "rust-lang.rust-analyzer" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Tauri + Svelte + Typescript 2 | 3 | This template should help get you started developing with Tauri, Svelte and TypeScript in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer). 8 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tauri + Svelte + TS 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-v2-beta", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "check": "svelte-check --tsconfig ./tsconfig.json", 11 | "tauri": "tauri" 12 | }, 13 | "dependencies": { 14 | "@tauri-apps/api": "2.0.3", 15 | "@tauri-apps/plugin-shell": "^2.0.0", 16 | "taurpc": "^1.7.0" 17 | }, 18 | "devDependencies": { 19 | "@sveltejs/vite-plugin-svelte": "^3.0.1", 20 | "@tauri-apps/cli": "2.0.3", 21 | "@tsconfig/svelte": "^5.0.2", 22 | "svelte": "^4.2.8", 23 | "svelte-check": "^3.4.6", 24 | "tslib": "^2.6.0", 25 | "typescript": "^5.0.2", 26 | "vite": "^5.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | '@tauri-apps/api': 12 | specifier: 2.0.3 13 | version: 2.0.3 14 | '@tauri-apps/plugin-shell': 15 | specifier: ^2.0.0 16 | version: 2.0.1 17 | taurpc: 18 | specifier: ^1.7.0 19 | version: link:.. 20 | devDependencies: 21 | '@sveltejs/vite-plugin-svelte': 22 | specifier: ^3.0.1 23 | version: 3.0.1(svelte@4.2.8)(vite@5.0.0) 24 | '@tauri-apps/cli': 25 | specifier: 2.0.3 26 | version: 2.0.3 27 | '@tsconfig/svelte': 28 | specifier: ^5.0.2 29 | version: 5.0.2 30 | svelte: 31 | specifier: ^4.2.8 32 | version: 4.2.8 33 | svelte-check: 34 | specifier: ^3.4.6 35 | version: 3.4.6(postcss@8.4.38)(svelte@4.2.8) 36 | tslib: 37 | specifier: ^2.6.0 38 | version: 2.6.0 39 | typescript: 40 | specifier: ^5.0.2 41 | version: 5.0.2 42 | vite: 43 | specifier: ^5.0.0 44 | version: 5.0.0 45 | 46 | packages: 47 | 48 | '@ampproject/remapping@2.3.0': 49 | resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} 50 | engines: {node: '>=6.0.0'} 51 | 52 | '@esbuild/aix-ppc64@0.19.12': 53 | resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} 54 | engines: {node: '>=12'} 55 | cpu: [ppc64] 56 | os: [aix] 57 | 58 | '@esbuild/android-arm64@0.19.12': 59 | resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} 60 | engines: {node: '>=12'} 61 | cpu: [arm64] 62 | os: [android] 63 | 64 | '@esbuild/android-arm@0.19.12': 65 | resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} 66 | engines: {node: '>=12'} 67 | cpu: [arm] 68 | os: [android] 69 | 70 | '@esbuild/android-x64@0.19.12': 71 | resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} 72 | engines: {node: '>=12'} 73 | cpu: [x64] 74 | os: [android] 75 | 76 | '@esbuild/darwin-arm64@0.19.12': 77 | resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} 78 | engines: {node: '>=12'} 79 | cpu: [arm64] 80 | os: [darwin] 81 | 82 | '@esbuild/darwin-x64@0.19.12': 83 | resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} 84 | engines: {node: '>=12'} 85 | cpu: [x64] 86 | os: [darwin] 87 | 88 | '@esbuild/freebsd-arm64@0.19.12': 89 | resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} 90 | engines: {node: '>=12'} 91 | cpu: [arm64] 92 | os: [freebsd] 93 | 94 | '@esbuild/freebsd-x64@0.19.12': 95 | resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} 96 | engines: {node: '>=12'} 97 | cpu: [x64] 98 | os: [freebsd] 99 | 100 | '@esbuild/linux-arm64@0.19.12': 101 | resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} 102 | engines: {node: '>=12'} 103 | cpu: [arm64] 104 | os: [linux] 105 | 106 | '@esbuild/linux-arm@0.19.12': 107 | resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} 108 | engines: {node: '>=12'} 109 | cpu: [arm] 110 | os: [linux] 111 | 112 | '@esbuild/linux-ia32@0.19.12': 113 | resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} 114 | engines: {node: '>=12'} 115 | cpu: [ia32] 116 | os: [linux] 117 | 118 | '@esbuild/linux-loong64@0.19.12': 119 | resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} 120 | engines: {node: '>=12'} 121 | cpu: [loong64] 122 | os: [linux] 123 | 124 | '@esbuild/linux-mips64el@0.19.12': 125 | resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} 126 | engines: {node: '>=12'} 127 | cpu: [mips64el] 128 | os: [linux] 129 | 130 | '@esbuild/linux-ppc64@0.19.12': 131 | resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} 132 | engines: {node: '>=12'} 133 | cpu: [ppc64] 134 | os: [linux] 135 | 136 | '@esbuild/linux-riscv64@0.19.12': 137 | resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} 138 | engines: {node: '>=12'} 139 | cpu: [riscv64] 140 | os: [linux] 141 | 142 | '@esbuild/linux-s390x@0.19.12': 143 | resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} 144 | engines: {node: '>=12'} 145 | cpu: [s390x] 146 | os: [linux] 147 | 148 | '@esbuild/linux-x64@0.19.12': 149 | resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} 150 | engines: {node: '>=12'} 151 | cpu: [x64] 152 | os: [linux] 153 | 154 | '@esbuild/netbsd-x64@0.19.12': 155 | resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} 156 | engines: {node: '>=12'} 157 | cpu: [x64] 158 | os: [netbsd] 159 | 160 | '@esbuild/openbsd-x64@0.19.12': 161 | resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} 162 | engines: {node: '>=12'} 163 | cpu: [x64] 164 | os: [openbsd] 165 | 166 | '@esbuild/sunos-x64@0.19.12': 167 | resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} 168 | engines: {node: '>=12'} 169 | cpu: [x64] 170 | os: [sunos] 171 | 172 | '@esbuild/win32-arm64@0.19.12': 173 | resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} 174 | engines: {node: '>=12'} 175 | cpu: [arm64] 176 | os: [win32] 177 | 178 | '@esbuild/win32-ia32@0.19.12': 179 | resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} 180 | engines: {node: '>=12'} 181 | cpu: [ia32] 182 | os: [win32] 183 | 184 | '@esbuild/win32-x64@0.19.12': 185 | resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} 186 | engines: {node: '>=12'} 187 | cpu: [x64] 188 | os: [win32] 189 | 190 | '@jridgewell/gen-mapping@0.3.5': 191 | resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} 192 | engines: {node: '>=6.0.0'} 193 | 194 | '@jridgewell/resolve-uri@3.1.2': 195 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 196 | engines: {node: '>=6.0.0'} 197 | 198 | '@jridgewell/set-array@1.2.1': 199 | resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 200 | engines: {node: '>=6.0.0'} 201 | 202 | '@jridgewell/sourcemap-codec@1.4.15': 203 | resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} 204 | 205 | '@jridgewell/trace-mapping@0.3.25': 206 | resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 207 | 208 | '@nodelib/fs.scandir@2.1.5': 209 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 210 | engines: {node: '>= 8'} 211 | 212 | '@nodelib/fs.stat@2.0.5': 213 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 214 | engines: {node: '>= 8'} 215 | 216 | '@nodelib/fs.walk@1.2.8': 217 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 218 | engines: {node: '>= 8'} 219 | 220 | '@rollup/rollup-android-arm-eabi@4.13.2': 221 | resolution: {integrity: sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g==} 222 | cpu: [arm] 223 | os: [android] 224 | 225 | '@rollup/rollup-android-arm64@4.13.2': 226 | resolution: {integrity: sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ==} 227 | cpu: [arm64] 228 | os: [android] 229 | 230 | '@rollup/rollup-darwin-arm64@4.13.2': 231 | resolution: {integrity: sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==} 232 | cpu: [arm64] 233 | os: [darwin] 234 | 235 | '@rollup/rollup-darwin-x64@4.13.2': 236 | resolution: {integrity: sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==} 237 | cpu: [x64] 238 | os: [darwin] 239 | 240 | '@rollup/rollup-linux-arm-gnueabihf@4.13.2': 241 | resolution: {integrity: sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ==} 242 | cpu: [arm] 243 | os: [linux] 244 | 245 | '@rollup/rollup-linux-arm64-gnu@4.13.2': 246 | resolution: {integrity: sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==} 247 | cpu: [arm64] 248 | os: [linux] 249 | 250 | '@rollup/rollup-linux-arm64-musl@4.13.2': 251 | resolution: {integrity: sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA==} 252 | cpu: [arm64] 253 | os: [linux] 254 | 255 | '@rollup/rollup-linux-powerpc64le-gnu@4.13.2': 256 | resolution: {integrity: sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ==} 257 | cpu: [ppc64le] 258 | os: [linux] 259 | 260 | '@rollup/rollup-linux-riscv64-gnu@4.13.2': 261 | resolution: {integrity: sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw==} 262 | cpu: [riscv64] 263 | os: [linux] 264 | 265 | '@rollup/rollup-linux-s390x-gnu@4.13.2': 266 | resolution: {integrity: sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg==} 267 | cpu: [s390x] 268 | os: [linux] 269 | 270 | '@rollup/rollup-linux-x64-gnu@4.13.2': 271 | resolution: {integrity: sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==} 272 | cpu: [x64] 273 | os: [linux] 274 | 275 | '@rollup/rollup-linux-x64-musl@4.13.2': 276 | resolution: {integrity: sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA==} 277 | cpu: [x64] 278 | os: [linux] 279 | 280 | '@rollup/rollup-win32-arm64-msvc@4.13.2': 281 | resolution: {integrity: sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA==} 282 | cpu: [arm64] 283 | os: [win32] 284 | 285 | '@rollup/rollup-win32-ia32-msvc@4.13.2': 286 | resolution: {integrity: sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw==} 287 | cpu: [ia32] 288 | os: [win32] 289 | 290 | '@rollup/rollup-win32-x64-msvc@4.13.2': 291 | resolution: {integrity: sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ==} 292 | cpu: [x64] 293 | os: [win32] 294 | 295 | '@sveltejs/vite-plugin-svelte-inspector@2.0.0': 296 | resolution: {integrity: sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==} 297 | engines: {node: ^18.0.0 || >=20} 298 | peerDependencies: 299 | '@sveltejs/vite-plugin-svelte': ^3.0.0 300 | svelte: ^4.0.0 || ^5.0.0-next.0 301 | vite: ^5.0.0 302 | 303 | '@sveltejs/vite-plugin-svelte@3.0.1': 304 | resolution: {integrity: sha512-CGURX6Ps+TkOovK6xV+Y2rn8JKa8ZPUHPZ/NKgCxAmgBrXReavzFl8aOSCj3kQ1xqT7yGJj53hjcV/gqwDAaWA==} 305 | engines: {node: ^18.0.0 || >=20} 306 | peerDependencies: 307 | svelte: ^4.0.0 || ^5.0.0-next.0 308 | vite: ^5.0.0 309 | 310 | '@tauri-apps/api@2.0.3': 311 | resolution: {integrity: sha512-840qk6n8rbXBXMA5/aAgTYsg5JAubKO0nXw5wf7IzGnUuYKGbB4oFBIZtXOIWy+E0kNTDI3qhq5iqsoMJfwp8g==} 312 | 313 | '@tauri-apps/cli-darwin-arm64@2.0.3': 314 | resolution: {integrity: sha512-jIsbxGWS+As1ZN7umo90nkql/ZAbrDK0GBT6UsgHSz5zSwwArICsZFFwE1pLZip5yoiV5mn3TGG2c1+v+0puzQ==} 315 | engines: {node: '>= 10'} 316 | cpu: [arm64] 317 | os: [darwin] 318 | 319 | '@tauri-apps/cli-darwin-x64@2.0.3': 320 | resolution: {integrity: sha512-ROITHtLTA1muyrwgyuwyasmaLCGtT4as/Kd1kerXaSDtFcYrnxiM984ZD0+FDUEDl5BgXtYa/sKKkKQFjgmM0A==} 321 | engines: {node: '>= 10'} 322 | cpu: [x64] 323 | os: [darwin] 324 | 325 | '@tauri-apps/cli-linux-arm-gnueabihf@2.0.3': 326 | resolution: {integrity: sha512-bQ3EZwCFfrLg/ZQ2I8sLuifSxESz4TP56SleTkKsPtTIZgNnKpM88PRDz4neiRroHVOq8NK0X276qi9LjGcXPw==} 327 | engines: {node: '>= 10'} 328 | cpu: [arm] 329 | os: [linux] 330 | 331 | '@tauri-apps/cli-linux-arm64-gnu@2.0.3': 332 | resolution: {integrity: sha512-aLfAA8P9OTErVUk3sATxtXqpAtlfDPMPp4fGjDysEELG/MyekGhmh2k/kG/i32OdPeCfO+Nr37wJksARJKubGw==} 333 | engines: {node: '>= 10'} 334 | cpu: [arm64] 335 | os: [linux] 336 | 337 | '@tauri-apps/cli-linux-arm64-musl@2.0.3': 338 | resolution: {integrity: sha512-I4MVD7nf6lLLRmNQPpe5beEIFM6q7Zkmh77ROA5BNu/+vHNL5kiTMD+bmd10ZL2r753A6pO7AvqkIxcBuIl0tg==} 339 | engines: {node: '>= 10'} 340 | cpu: [arm64] 341 | os: [linux] 342 | 343 | '@tauri-apps/cli-linux-x64-gnu@2.0.3': 344 | resolution: {integrity: sha512-C6Jkx2zZGKkoi+sg5FK9GoH/0EvAaOgrZfF5azV5EALGba46g7VpWcZgp9zFUd7K2IzTi+0OOY8TQ2OVfKZgew==} 345 | engines: {node: '>= 10'} 346 | cpu: [x64] 347 | os: [linux] 348 | 349 | '@tauri-apps/cli-linux-x64-musl@2.0.3': 350 | resolution: {integrity: sha512-qi4ghmTfSAl+EEUDwmwI9AJUiOLNSmU1RgiGgcPRE+7A/W+Am9UnxYySAiRbB/gJgTl9sj/pqH5Y9duP1/sqHg==} 351 | engines: {node: '>= 10'} 352 | cpu: [x64] 353 | os: [linux] 354 | 355 | '@tauri-apps/cli-win32-arm64-msvc@2.0.3': 356 | resolution: {integrity: sha512-UXxHkYmFesC97qVmZre4vY7oDxRDtC2OeKNv0bH+iSnuUp/ROxzJYGyaelnv9Ybvgl4YVqDCnxgB28qMM938TA==} 357 | engines: {node: '>= 10'} 358 | cpu: [arm64] 359 | os: [win32] 360 | 361 | '@tauri-apps/cli-win32-ia32-msvc@2.0.3': 362 | resolution: {integrity: sha512-D+xoaa35RGlkXDpnL5uDTpj29untuC5Wp6bN9snfgFDagD0wnFfC8+2ZQGu16bD0IteWqDI0OSoIXhNvy+F+wg==} 363 | engines: {node: '>= 10'} 364 | cpu: [ia32] 365 | os: [win32] 366 | 367 | '@tauri-apps/cli-win32-x64-msvc@2.0.3': 368 | resolution: {integrity: sha512-eWV9XWb4dSYHXl13OtYWLjX1JHphUEkHkkGwJrhr8qFBm7RbxXxQvrsUEprSi51ug/dwJenjJgM4zR8By4htfw==} 369 | engines: {node: '>= 10'} 370 | cpu: [x64] 371 | os: [win32] 372 | 373 | '@tauri-apps/cli@2.0.3': 374 | resolution: {integrity: sha512-JwEyhc5BAVpn4E8kxzY/h7+bVOiXQdudR1r3ODMfyyumZBfgIWqpD/WuTcPq6Yjchju1BSS+80jAE/oYwI/RKg==} 375 | engines: {node: '>= 10'} 376 | hasBin: true 377 | 378 | '@tauri-apps/plugin-shell@2.0.1': 379 | resolution: {integrity: sha512-akU1b77sw3qHiynrK0s930y8zKmcdrSD60htjH+mFZqv5WaakZA/XxHR3/sF1nNv9Mgmt/Shls37HwnOr00aSw==} 380 | 381 | '@tsconfig/svelte@5.0.2': 382 | resolution: {integrity: sha512-BRbo1fOtyVbhfLyuCWw6wAWp+U8UQle+ZXu84MYYWzYSEB28dyfnRBIE99eoG+qdAC0po6L2ScIEivcT07UaMA==} 383 | 384 | '@types/estree@1.0.5': 385 | resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} 386 | 387 | '@types/pug@2.0.10': 388 | resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} 389 | 390 | acorn@8.11.3: 391 | resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} 392 | engines: {node: '>=0.4.0'} 393 | hasBin: true 394 | 395 | anymatch@3.1.3: 396 | resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} 397 | engines: {node: '>= 8'} 398 | 399 | aria-query@5.3.0: 400 | resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} 401 | 402 | axobject-query@3.2.1: 403 | resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} 404 | 405 | balanced-match@1.0.2: 406 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 407 | 408 | binary-extensions@2.3.0: 409 | resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} 410 | engines: {node: '>=8'} 411 | 412 | brace-expansion@1.1.11: 413 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 414 | 415 | braces@3.0.2: 416 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 417 | engines: {node: '>=8'} 418 | 419 | buffer-crc32@0.2.13: 420 | resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} 421 | 422 | callsites@3.1.0: 423 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 424 | engines: {node: '>=6'} 425 | 426 | chokidar@3.6.0: 427 | resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} 428 | engines: {node: '>= 8.10.0'} 429 | 430 | code-red@1.0.4: 431 | resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} 432 | 433 | concat-map@0.0.1: 434 | resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} 435 | 436 | css-tree@2.3.1: 437 | resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} 438 | engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} 439 | 440 | debug@4.3.4: 441 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 442 | engines: {node: '>=6.0'} 443 | peerDependencies: 444 | supports-color: '*' 445 | peerDependenciesMeta: 446 | supports-color: 447 | optional: true 448 | 449 | deepmerge@4.3.1: 450 | resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} 451 | engines: {node: '>=0.10.0'} 452 | 453 | dequal@2.0.3: 454 | resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} 455 | engines: {node: '>=6'} 456 | 457 | detect-indent@6.1.0: 458 | resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} 459 | engines: {node: '>=8'} 460 | 461 | es6-promise@3.3.1: 462 | resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} 463 | 464 | esbuild@0.19.12: 465 | resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} 466 | engines: {node: '>=12'} 467 | hasBin: true 468 | 469 | estree-walker@3.0.3: 470 | resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} 471 | 472 | fast-glob@3.3.2: 473 | resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} 474 | engines: {node: '>=8.6.0'} 475 | 476 | fastq@1.17.1: 477 | resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} 478 | 479 | fill-range@7.0.1: 480 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 481 | engines: {node: '>=8'} 482 | 483 | fs.realpath@1.0.0: 484 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 485 | 486 | fsevents@2.3.3: 487 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 488 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 489 | os: [darwin] 490 | 491 | glob-parent@5.1.2: 492 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 493 | engines: {node: '>= 6'} 494 | 495 | glob@7.2.3: 496 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 497 | 498 | graceful-fs@4.2.11: 499 | resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 500 | 501 | import-fresh@3.3.0: 502 | resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} 503 | engines: {node: '>=6'} 504 | 505 | inflight@1.0.6: 506 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 507 | 508 | inherits@2.0.4: 509 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 510 | 511 | is-binary-path@2.1.0: 512 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 513 | engines: {node: '>=8'} 514 | 515 | is-extglob@2.1.1: 516 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 517 | engines: {node: '>=0.10.0'} 518 | 519 | is-glob@4.0.3: 520 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 521 | engines: {node: '>=0.10.0'} 522 | 523 | is-number@7.0.0: 524 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 525 | engines: {node: '>=0.12.0'} 526 | 527 | is-reference@3.0.2: 528 | resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} 529 | 530 | kleur@4.1.5: 531 | resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} 532 | engines: {node: '>=6'} 533 | 534 | locate-character@3.0.0: 535 | resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} 536 | 537 | magic-string@0.30.8: 538 | resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} 539 | engines: {node: '>=12'} 540 | 541 | mdn-data@2.0.30: 542 | resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} 543 | 544 | merge2@1.4.1: 545 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 546 | engines: {node: '>= 8'} 547 | 548 | micromatch@4.0.5: 549 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 550 | engines: {node: '>=8.6'} 551 | 552 | min-indent@1.0.1: 553 | resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} 554 | engines: {node: '>=4'} 555 | 556 | minimatch@3.1.2: 557 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 558 | 559 | minimist@1.2.8: 560 | resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 561 | 562 | mkdirp@0.5.6: 563 | resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} 564 | hasBin: true 565 | 566 | mri@1.2.0: 567 | resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} 568 | engines: {node: '>=4'} 569 | 570 | ms@2.1.2: 571 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 572 | 573 | nanoid@3.3.7: 574 | resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} 575 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 576 | hasBin: true 577 | 578 | normalize-path@3.0.0: 579 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 580 | engines: {node: '>=0.10.0'} 581 | 582 | once@1.4.0: 583 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 584 | 585 | parent-module@1.0.1: 586 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 587 | engines: {node: '>=6'} 588 | 589 | path-is-absolute@1.0.1: 590 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 591 | engines: {node: '>=0.10.0'} 592 | 593 | periscopic@3.1.0: 594 | resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} 595 | 596 | picocolors@1.0.0: 597 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 598 | 599 | picomatch@2.3.1: 600 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 601 | engines: {node: '>=8.6'} 602 | 603 | postcss@8.4.38: 604 | resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} 605 | engines: {node: ^10 || ^12 || >=14} 606 | 607 | queue-microtask@1.2.3: 608 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 609 | 610 | readdirp@3.6.0: 611 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 612 | engines: {node: '>=8.10.0'} 613 | 614 | resolve-from@4.0.0: 615 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 616 | engines: {node: '>=4'} 617 | 618 | reusify@1.0.4: 619 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 620 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 621 | 622 | rimraf@2.7.1: 623 | resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} 624 | hasBin: true 625 | 626 | rollup@4.13.2: 627 | resolution: {integrity: sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g==} 628 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 629 | hasBin: true 630 | 631 | run-parallel@1.2.0: 632 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 633 | 634 | sade@1.8.1: 635 | resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} 636 | engines: {node: '>=6'} 637 | 638 | sander@0.5.1: 639 | resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} 640 | 641 | sorcery@0.11.0: 642 | resolution: {integrity: sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==} 643 | hasBin: true 644 | 645 | source-map-js@1.2.0: 646 | resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} 647 | engines: {node: '>=0.10.0'} 648 | 649 | strip-indent@3.0.0: 650 | resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} 651 | engines: {node: '>=8'} 652 | 653 | svelte-check@3.4.6: 654 | resolution: {integrity: sha512-OBlY8866Zh1zHQTkBMPS6psPi7o2umTUyj6JWm4SacnIHXpWFm658pG32m3dKvKFL49V4ntAkfFHKo4ztH07og==} 655 | hasBin: true 656 | peerDependencies: 657 | svelte: ^3.55.0 || ^4.0.0-next.0 || ^4.0.0 658 | 659 | svelte-hmr@0.15.3: 660 | resolution: {integrity: sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==} 661 | engines: {node: ^12.20 || ^14.13.1 || >= 16} 662 | peerDependencies: 663 | svelte: ^3.19.0 || ^4.0.0 664 | 665 | svelte-preprocess@5.1.3: 666 | resolution: {integrity: sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==} 667 | engines: {node: '>= 16.0.0', pnpm: ^8.0.0} 668 | peerDependencies: 669 | '@babel/core': ^7.10.2 670 | coffeescript: ^2.5.1 671 | less: ^3.11.3 || ^4.0.0 672 | postcss: ^7 || ^8 673 | postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 674 | pug: ^3.0.0 675 | sass: ^1.26.8 676 | stylus: ^0.55.0 677 | sugarss: ^2.0.0 || ^3.0.0 || ^4.0.0 678 | svelte: ^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0 679 | typescript: '>=3.9.5 || ^4.0.0 || ^5.0.0' 680 | peerDependenciesMeta: 681 | '@babel/core': 682 | optional: true 683 | coffeescript: 684 | optional: true 685 | less: 686 | optional: true 687 | postcss: 688 | optional: true 689 | postcss-load-config: 690 | optional: true 691 | pug: 692 | optional: true 693 | sass: 694 | optional: true 695 | stylus: 696 | optional: true 697 | sugarss: 698 | optional: true 699 | typescript: 700 | optional: true 701 | 702 | svelte@4.2.8: 703 | resolution: {integrity: sha512-hU6dh1MPl8gh6klQZwK/n73GiAHiR95IkFsesLPbMeEZi36ydaXL/ZAb4g9sayT0MXzpxyZjR28yderJHxcmYA==} 704 | engines: {node: '>=16'} 705 | 706 | to-regex-range@5.0.1: 707 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 708 | engines: {node: '>=8.0'} 709 | 710 | tslib@2.6.0: 711 | resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==} 712 | 713 | typescript@5.0.2: 714 | resolution: {integrity: sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==} 715 | engines: {node: '>=12.20'} 716 | hasBin: true 717 | 718 | typescript@5.4.3: 719 | resolution: {integrity: sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==} 720 | engines: {node: '>=14.17'} 721 | hasBin: true 722 | 723 | vite@5.0.0: 724 | resolution: {integrity: sha512-ESJVM59mdyGpsiNAeHQOR/0fqNoOyWPYesFto8FFZugfmhdHx8Fzd8sF3Q/xkVhZsyOxHfdM7ieiVAorI9RjFw==} 725 | engines: {node: ^18.0.0 || >=20.0.0} 726 | hasBin: true 727 | peerDependencies: 728 | '@types/node': ^18.0.0 || >=20.0.0 729 | less: '*' 730 | lightningcss: ^1.21.0 731 | sass: '*' 732 | stylus: '*' 733 | sugarss: '*' 734 | terser: ^5.4.0 735 | peerDependenciesMeta: 736 | '@types/node': 737 | optional: true 738 | less: 739 | optional: true 740 | lightningcss: 741 | optional: true 742 | sass: 743 | optional: true 744 | stylus: 745 | optional: true 746 | sugarss: 747 | optional: true 748 | terser: 749 | optional: true 750 | 751 | vitefu@0.2.5: 752 | resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==} 753 | peerDependencies: 754 | vite: ^3.0.0 || ^4.0.0 || ^5.0.0 755 | peerDependenciesMeta: 756 | vite: 757 | optional: true 758 | 759 | wrappy@1.0.2: 760 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 761 | 762 | snapshots: 763 | 764 | '@ampproject/remapping@2.3.0': 765 | dependencies: 766 | '@jridgewell/gen-mapping': 0.3.5 767 | '@jridgewell/trace-mapping': 0.3.25 768 | 769 | '@esbuild/aix-ppc64@0.19.12': 770 | optional: true 771 | 772 | '@esbuild/android-arm64@0.19.12': 773 | optional: true 774 | 775 | '@esbuild/android-arm@0.19.12': 776 | optional: true 777 | 778 | '@esbuild/android-x64@0.19.12': 779 | optional: true 780 | 781 | '@esbuild/darwin-arm64@0.19.12': 782 | optional: true 783 | 784 | '@esbuild/darwin-x64@0.19.12': 785 | optional: true 786 | 787 | '@esbuild/freebsd-arm64@0.19.12': 788 | optional: true 789 | 790 | '@esbuild/freebsd-x64@0.19.12': 791 | optional: true 792 | 793 | '@esbuild/linux-arm64@0.19.12': 794 | optional: true 795 | 796 | '@esbuild/linux-arm@0.19.12': 797 | optional: true 798 | 799 | '@esbuild/linux-ia32@0.19.12': 800 | optional: true 801 | 802 | '@esbuild/linux-loong64@0.19.12': 803 | optional: true 804 | 805 | '@esbuild/linux-mips64el@0.19.12': 806 | optional: true 807 | 808 | '@esbuild/linux-ppc64@0.19.12': 809 | optional: true 810 | 811 | '@esbuild/linux-riscv64@0.19.12': 812 | optional: true 813 | 814 | '@esbuild/linux-s390x@0.19.12': 815 | optional: true 816 | 817 | '@esbuild/linux-x64@0.19.12': 818 | optional: true 819 | 820 | '@esbuild/netbsd-x64@0.19.12': 821 | optional: true 822 | 823 | '@esbuild/openbsd-x64@0.19.12': 824 | optional: true 825 | 826 | '@esbuild/sunos-x64@0.19.12': 827 | optional: true 828 | 829 | '@esbuild/win32-arm64@0.19.12': 830 | optional: true 831 | 832 | '@esbuild/win32-ia32@0.19.12': 833 | optional: true 834 | 835 | '@esbuild/win32-x64@0.19.12': 836 | optional: true 837 | 838 | '@jridgewell/gen-mapping@0.3.5': 839 | dependencies: 840 | '@jridgewell/set-array': 1.2.1 841 | '@jridgewell/sourcemap-codec': 1.4.15 842 | '@jridgewell/trace-mapping': 0.3.25 843 | 844 | '@jridgewell/resolve-uri@3.1.2': {} 845 | 846 | '@jridgewell/set-array@1.2.1': {} 847 | 848 | '@jridgewell/sourcemap-codec@1.4.15': {} 849 | 850 | '@jridgewell/trace-mapping@0.3.25': 851 | dependencies: 852 | '@jridgewell/resolve-uri': 3.1.2 853 | '@jridgewell/sourcemap-codec': 1.4.15 854 | 855 | '@nodelib/fs.scandir@2.1.5': 856 | dependencies: 857 | '@nodelib/fs.stat': 2.0.5 858 | run-parallel: 1.2.0 859 | 860 | '@nodelib/fs.stat@2.0.5': {} 861 | 862 | '@nodelib/fs.walk@1.2.8': 863 | dependencies: 864 | '@nodelib/fs.scandir': 2.1.5 865 | fastq: 1.17.1 866 | 867 | '@rollup/rollup-android-arm-eabi@4.13.2': 868 | optional: true 869 | 870 | '@rollup/rollup-android-arm64@4.13.2': 871 | optional: true 872 | 873 | '@rollup/rollup-darwin-arm64@4.13.2': 874 | optional: true 875 | 876 | '@rollup/rollup-darwin-x64@4.13.2': 877 | optional: true 878 | 879 | '@rollup/rollup-linux-arm-gnueabihf@4.13.2': 880 | optional: true 881 | 882 | '@rollup/rollup-linux-arm64-gnu@4.13.2': 883 | optional: true 884 | 885 | '@rollup/rollup-linux-arm64-musl@4.13.2': 886 | optional: true 887 | 888 | '@rollup/rollup-linux-powerpc64le-gnu@4.13.2': 889 | optional: true 890 | 891 | '@rollup/rollup-linux-riscv64-gnu@4.13.2': 892 | optional: true 893 | 894 | '@rollup/rollup-linux-s390x-gnu@4.13.2': 895 | optional: true 896 | 897 | '@rollup/rollup-linux-x64-gnu@4.13.2': 898 | optional: true 899 | 900 | '@rollup/rollup-linux-x64-musl@4.13.2': 901 | optional: true 902 | 903 | '@rollup/rollup-win32-arm64-msvc@4.13.2': 904 | optional: true 905 | 906 | '@rollup/rollup-win32-ia32-msvc@4.13.2': 907 | optional: true 908 | 909 | '@rollup/rollup-win32-x64-msvc@4.13.2': 910 | optional: true 911 | 912 | '@sveltejs/vite-plugin-svelte-inspector@2.0.0(@sveltejs/vite-plugin-svelte@3.0.1(svelte@4.2.8)(vite@5.0.0))(svelte@4.2.8)(vite@5.0.0)': 913 | dependencies: 914 | '@sveltejs/vite-plugin-svelte': 3.0.1(svelte@4.2.8)(vite@5.0.0) 915 | debug: 4.3.4 916 | svelte: 4.2.8 917 | vite: 5.0.0 918 | transitivePeerDependencies: 919 | - supports-color 920 | 921 | '@sveltejs/vite-plugin-svelte@3.0.1(svelte@4.2.8)(vite@5.0.0)': 922 | dependencies: 923 | '@sveltejs/vite-plugin-svelte-inspector': 2.0.0(@sveltejs/vite-plugin-svelte@3.0.1(svelte@4.2.8)(vite@5.0.0))(svelte@4.2.8)(vite@5.0.0) 924 | debug: 4.3.4 925 | deepmerge: 4.3.1 926 | kleur: 4.1.5 927 | magic-string: 0.30.8 928 | svelte: 4.2.8 929 | svelte-hmr: 0.15.3(svelte@4.2.8) 930 | vite: 5.0.0 931 | vitefu: 0.2.5(vite@5.0.0) 932 | transitivePeerDependencies: 933 | - supports-color 934 | 935 | '@tauri-apps/api@2.0.3': {} 936 | 937 | '@tauri-apps/cli-darwin-arm64@2.0.3': 938 | optional: true 939 | 940 | '@tauri-apps/cli-darwin-x64@2.0.3': 941 | optional: true 942 | 943 | '@tauri-apps/cli-linux-arm-gnueabihf@2.0.3': 944 | optional: true 945 | 946 | '@tauri-apps/cli-linux-arm64-gnu@2.0.3': 947 | optional: true 948 | 949 | '@tauri-apps/cli-linux-arm64-musl@2.0.3': 950 | optional: true 951 | 952 | '@tauri-apps/cli-linux-x64-gnu@2.0.3': 953 | optional: true 954 | 955 | '@tauri-apps/cli-linux-x64-musl@2.0.3': 956 | optional: true 957 | 958 | '@tauri-apps/cli-win32-arm64-msvc@2.0.3': 959 | optional: true 960 | 961 | '@tauri-apps/cli-win32-ia32-msvc@2.0.3': 962 | optional: true 963 | 964 | '@tauri-apps/cli-win32-x64-msvc@2.0.3': 965 | optional: true 966 | 967 | '@tauri-apps/cli@2.0.3': 968 | optionalDependencies: 969 | '@tauri-apps/cli-darwin-arm64': 2.0.3 970 | '@tauri-apps/cli-darwin-x64': 2.0.3 971 | '@tauri-apps/cli-linux-arm-gnueabihf': 2.0.3 972 | '@tauri-apps/cli-linux-arm64-gnu': 2.0.3 973 | '@tauri-apps/cli-linux-arm64-musl': 2.0.3 974 | '@tauri-apps/cli-linux-x64-gnu': 2.0.3 975 | '@tauri-apps/cli-linux-x64-musl': 2.0.3 976 | '@tauri-apps/cli-win32-arm64-msvc': 2.0.3 977 | '@tauri-apps/cli-win32-ia32-msvc': 2.0.3 978 | '@tauri-apps/cli-win32-x64-msvc': 2.0.3 979 | 980 | '@tauri-apps/plugin-shell@2.0.1': 981 | dependencies: 982 | '@tauri-apps/api': 2.0.3 983 | 984 | '@tsconfig/svelte@5.0.2': {} 985 | 986 | '@types/estree@1.0.5': {} 987 | 988 | '@types/pug@2.0.10': {} 989 | 990 | acorn@8.11.3: {} 991 | 992 | anymatch@3.1.3: 993 | dependencies: 994 | normalize-path: 3.0.0 995 | picomatch: 2.3.1 996 | 997 | aria-query@5.3.0: 998 | dependencies: 999 | dequal: 2.0.3 1000 | 1001 | axobject-query@3.2.1: 1002 | dependencies: 1003 | dequal: 2.0.3 1004 | 1005 | balanced-match@1.0.2: {} 1006 | 1007 | binary-extensions@2.3.0: {} 1008 | 1009 | brace-expansion@1.1.11: 1010 | dependencies: 1011 | balanced-match: 1.0.2 1012 | concat-map: 0.0.1 1013 | 1014 | braces@3.0.2: 1015 | dependencies: 1016 | fill-range: 7.0.1 1017 | 1018 | buffer-crc32@0.2.13: {} 1019 | 1020 | callsites@3.1.0: {} 1021 | 1022 | chokidar@3.6.0: 1023 | dependencies: 1024 | anymatch: 3.1.3 1025 | braces: 3.0.2 1026 | glob-parent: 5.1.2 1027 | is-binary-path: 2.1.0 1028 | is-glob: 4.0.3 1029 | normalize-path: 3.0.0 1030 | readdirp: 3.6.0 1031 | optionalDependencies: 1032 | fsevents: 2.3.3 1033 | 1034 | code-red@1.0.4: 1035 | dependencies: 1036 | '@jridgewell/sourcemap-codec': 1.4.15 1037 | '@types/estree': 1.0.5 1038 | acorn: 8.11.3 1039 | estree-walker: 3.0.3 1040 | periscopic: 3.1.0 1041 | 1042 | concat-map@0.0.1: {} 1043 | 1044 | css-tree@2.3.1: 1045 | dependencies: 1046 | mdn-data: 2.0.30 1047 | source-map-js: 1.2.0 1048 | 1049 | debug@4.3.4: 1050 | dependencies: 1051 | ms: 2.1.2 1052 | 1053 | deepmerge@4.3.1: {} 1054 | 1055 | dequal@2.0.3: {} 1056 | 1057 | detect-indent@6.1.0: {} 1058 | 1059 | es6-promise@3.3.1: {} 1060 | 1061 | esbuild@0.19.12: 1062 | optionalDependencies: 1063 | '@esbuild/aix-ppc64': 0.19.12 1064 | '@esbuild/android-arm': 0.19.12 1065 | '@esbuild/android-arm64': 0.19.12 1066 | '@esbuild/android-x64': 0.19.12 1067 | '@esbuild/darwin-arm64': 0.19.12 1068 | '@esbuild/darwin-x64': 0.19.12 1069 | '@esbuild/freebsd-arm64': 0.19.12 1070 | '@esbuild/freebsd-x64': 0.19.12 1071 | '@esbuild/linux-arm': 0.19.12 1072 | '@esbuild/linux-arm64': 0.19.12 1073 | '@esbuild/linux-ia32': 0.19.12 1074 | '@esbuild/linux-loong64': 0.19.12 1075 | '@esbuild/linux-mips64el': 0.19.12 1076 | '@esbuild/linux-ppc64': 0.19.12 1077 | '@esbuild/linux-riscv64': 0.19.12 1078 | '@esbuild/linux-s390x': 0.19.12 1079 | '@esbuild/linux-x64': 0.19.12 1080 | '@esbuild/netbsd-x64': 0.19.12 1081 | '@esbuild/openbsd-x64': 0.19.12 1082 | '@esbuild/sunos-x64': 0.19.12 1083 | '@esbuild/win32-arm64': 0.19.12 1084 | '@esbuild/win32-ia32': 0.19.12 1085 | '@esbuild/win32-x64': 0.19.12 1086 | 1087 | estree-walker@3.0.3: 1088 | dependencies: 1089 | '@types/estree': 1.0.5 1090 | 1091 | fast-glob@3.3.2: 1092 | dependencies: 1093 | '@nodelib/fs.stat': 2.0.5 1094 | '@nodelib/fs.walk': 1.2.8 1095 | glob-parent: 5.1.2 1096 | merge2: 1.4.1 1097 | micromatch: 4.0.5 1098 | 1099 | fastq@1.17.1: 1100 | dependencies: 1101 | reusify: 1.0.4 1102 | 1103 | fill-range@7.0.1: 1104 | dependencies: 1105 | to-regex-range: 5.0.1 1106 | 1107 | fs.realpath@1.0.0: {} 1108 | 1109 | fsevents@2.3.3: 1110 | optional: true 1111 | 1112 | glob-parent@5.1.2: 1113 | dependencies: 1114 | is-glob: 4.0.3 1115 | 1116 | glob@7.2.3: 1117 | dependencies: 1118 | fs.realpath: 1.0.0 1119 | inflight: 1.0.6 1120 | inherits: 2.0.4 1121 | minimatch: 3.1.2 1122 | once: 1.4.0 1123 | path-is-absolute: 1.0.1 1124 | 1125 | graceful-fs@4.2.11: {} 1126 | 1127 | import-fresh@3.3.0: 1128 | dependencies: 1129 | parent-module: 1.0.1 1130 | resolve-from: 4.0.0 1131 | 1132 | inflight@1.0.6: 1133 | dependencies: 1134 | once: 1.4.0 1135 | wrappy: 1.0.2 1136 | 1137 | inherits@2.0.4: {} 1138 | 1139 | is-binary-path@2.1.0: 1140 | dependencies: 1141 | binary-extensions: 2.3.0 1142 | 1143 | is-extglob@2.1.1: {} 1144 | 1145 | is-glob@4.0.3: 1146 | dependencies: 1147 | is-extglob: 2.1.1 1148 | 1149 | is-number@7.0.0: {} 1150 | 1151 | is-reference@3.0.2: 1152 | dependencies: 1153 | '@types/estree': 1.0.5 1154 | 1155 | kleur@4.1.5: {} 1156 | 1157 | locate-character@3.0.0: {} 1158 | 1159 | magic-string@0.30.8: 1160 | dependencies: 1161 | '@jridgewell/sourcemap-codec': 1.4.15 1162 | 1163 | mdn-data@2.0.30: {} 1164 | 1165 | merge2@1.4.1: {} 1166 | 1167 | micromatch@4.0.5: 1168 | dependencies: 1169 | braces: 3.0.2 1170 | picomatch: 2.3.1 1171 | 1172 | min-indent@1.0.1: {} 1173 | 1174 | minimatch@3.1.2: 1175 | dependencies: 1176 | brace-expansion: 1.1.11 1177 | 1178 | minimist@1.2.8: {} 1179 | 1180 | mkdirp@0.5.6: 1181 | dependencies: 1182 | minimist: 1.2.8 1183 | 1184 | mri@1.2.0: {} 1185 | 1186 | ms@2.1.2: {} 1187 | 1188 | nanoid@3.3.7: {} 1189 | 1190 | normalize-path@3.0.0: {} 1191 | 1192 | once@1.4.0: 1193 | dependencies: 1194 | wrappy: 1.0.2 1195 | 1196 | parent-module@1.0.1: 1197 | dependencies: 1198 | callsites: 3.1.0 1199 | 1200 | path-is-absolute@1.0.1: {} 1201 | 1202 | periscopic@3.1.0: 1203 | dependencies: 1204 | '@types/estree': 1.0.5 1205 | estree-walker: 3.0.3 1206 | is-reference: 3.0.2 1207 | 1208 | picocolors@1.0.0: {} 1209 | 1210 | picomatch@2.3.1: {} 1211 | 1212 | postcss@8.4.38: 1213 | dependencies: 1214 | nanoid: 3.3.7 1215 | picocolors: 1.0.0 1216 | source-map-js: 1.2.0 1217 | 1218 | queue-microtask@1.2.3: {} 1219 | 1220 | readdirp@3.6.0: 1221 | dependencies: 1222 | picomatch: 2.3.1 1223 | 1224 | resolve-from@4.0.0: {} 1225 | 1226 | reusify@1.0.4: {} 1227 | 1228 | rimraf@2.7.1: 1229 | dependencies: 1230 | glob: 7.2.3 1231 | 1232 | rollup@4.13.2: 1233 | dependencies: 1234 | '@types/estree': 1.0.5 1235 | optionalDependencies: 1236 | '@rollup/rollup-android-arm-eabi': 4.13.2 1237 | '@rollup/rollup-android-arm64': 4.13.2 1238 | '@rollup/rollup-darwin-arm64': 4.13.2 1239 | '@rollup/rollup-darwin-x64': 4.13.2 1240 | '@rollup/rollup-linux-arm-gnueabihf': 4.13.2 1241 | '@rollup/rollup-linux-arm64-gnu': 4.13.2 1242 | '@rollup/rollup-linux-arm64-musl': 4.13.2 1243 | '@rollup/rollup-linux-powerpc64le-gnu': 4.13.2 1244 | '@rollup/rollup-linux-riscv64-gnu': 4.13.2 1245 | '@rollup/rollup-linux-s390x-gnu': 4.13.2 1246 | '@rollup/rollup-linux-x64-gnu': 4.13.2 1247 | '@rollup/rollup-linux-x64-musl': 4.13.2 1248 | '@rollup/rollup-win32-arm64-msvc': 4.13.2 1249 | '@rollup/rollup-win32-ia32-msvc': 4.13.2 1250 | '@rollup/rollup-win32-x64-msvc': 4.13.2 1251 | fsevents: 2.3.3 1252 | 1253 | run-parallel@1.2.0: 1254 | dependencies: 1255 | queue-microtask: 1.2.3 1256 | 1257 | sade@1.8.1: 1258 | dependencies: 1259 | mri: 1.2.0 1260 | 1261 | sander@0.5.1: 1262 | dependencies: 1263 | es6-promise: 3.3.1 1264 | graceful-fs: 4.2.11 1265 | mkdirp: 0.5.6 1266 | rimraf: 2.7.1 1267 | 1268 | sorcery@0.11.0: 1269 | dependencies: 1270 | '@jridgewell/sourcemap-codec': 1.4.15 1271 | buffer-crc32: 0.2.13 1272 | minimist: 1.2.8 1273 | sander: 0.5.1 1274 | 1275 | source-map-js@1.2.0: {} 1276 | 1277 | strip-indent@3.0.0: 1278 | dependencies: 1279 | min-indent: 1.0.1 1280 | 1281 | svelte-check@3.4.6(postcss@8.4.38)(svelte@4.2.8): 1282 | dependencies: 1283 | '@jridgewell/trace-mapping': 0.3.25 1284 | chokidar: 3.6.0 1285 | fast-glob: 3.3.2 1286 | import-fresh: 3.3.0 1287 | picocolors: 1.0.0 1288 | sade: 1.8.1 1289 | svelte: 4.2.8 1290 | svelte-preprocess: 5.1.3(postcss@8.4.38)(svelte@4.2.8)(typescript@5.4.3) 1291 | typescript: 5.4.3 1292 | transitivePeerDependencies: 1293 | - '@babel/core' 1294 | - coffeescript 1295 | - less 1296 | - postcss 1297 | - postcss-load-config 1298 | - pug 1299 | - sass 1300 | - stylus 1301 | - sugarss 1302 | 1303 | svelte-hmr@0.15.3(svelte@4.2.8): 1304 | dependencies: 1305 | svelte: 4.2.8 1306 | 1307 | svelte-preprocess@5.1.3(postcss@8.4.38)(svelte@4.2.8)(typescript@5.4.3): 1308 | dependencies: 1309 | '@types/pug': 2.0.10 1310 | detect-indent: 6.1.0 1311 | magic-string: 0.30.8 1312 | sorcery: 0.11.0 1313 | strip-indent: 3.0.0 1314 | svelte: 4.2.8 1315 | optionalDependencies: 1316 | postcss: 8.4.38 1317 | typescript: 5.4.3 1318 | 1319 | svelte@4.2.8: 1320 | dependencies: 1321 | '@ampproject/remapping': 2.3.0 1322 | '@jridgewell/sourcemap-codec': 1.4.15 1323 | '@jridgewell/trace-mapping': 0.3.25 1324 | acorn: 8.11.3 1325 | aria-query: 5.3.0 1326 | axobject-query: 3.2.1 1327 | code-red: 1.0.4 1328 | css-tree: 2.3.1 1329 | estree-walker: 3.0.3 1330 | is-reference: 3.0.2 1331 | locate-character: 3.0.0 1332 | magic-string: 0.30.8 1333 | periscopic: 3.1.0 1334 | 1335 | to-regex-range@5.0.1: 1336 | dependencies: 1337 | is-number: 7.0.0 1338 | 1339 | tslib@2.6.0: {} 1340 | 1341 | typescript@5.0.2: {} 1342 | 1343 | typescript@5.4.3: {} 1344 | 1345 | vite@5.0.0: 1346 | dependencies: 1347 | esbuild: 0.19.12 1348 | postcss: 8.4.38 1349 | rollup: 4.13.2 1350 | optionalDependencies: 1351 | fsevents: 2.3.3 1352 | 1353 | vitefu@0.2.5(vite@5.0.0): 1354 | optionalDependencies: 1355 | vite: 5.0.0 1356 | 1357 | wrappy@1.0.2: {} 1358 | -------------------------------------------------------------------------------- /example/public/svelte.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/public/tauri.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /example/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Generated by Tauri 6 | # will have schema files for capabilities auto-completion 7 | /gen/schemas 8 | -------------------------------------------------------------------------------- /example/src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-v2-beta" 3 | version = "0.0.0" 4 | description = "A Tauri App" 5 | authors = ["you"] 6 | edition = "2021" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [build-dependencies] 11 | tauri-build = { version = "2.0.0", features = [] } 12 | 13 | [dependencies] 14 | tauri = { version = "2.1.1", features = [] } 15 | tauri-plugin-shell = "2.0.2" 16 | serde = { version = "1.0.215", features = ["derive"] } 17 | serde_json = "1.0.133" 18 | 19 | tokio = { version = "1", features = ["full"] } 20 | thiserror = "1.0" 21 | 22 | taurpc = { path = "../../taurpc" } 23 | specta = { version = "=2.0.0-rc.22", features = ["derive", "function"] } 24 | specta-typescript = { version = "0.0.9", features = [] } 25 | -------------------------------------------------------------------------------- /example/src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /example/src-tauri/capabilities/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../gen/schemas/desktop-schema.json", 3 | "identifier": "default", 4 | "description": "Capability for the main window", 5 | "windows": ["main"], 6 | "permissions": [ 7 | "core:path:default", 8 | "core:event:default", 9 | "core:window:default", 10 | "core:app:default", 11 | "core:image:default", 12 | "core:resources:default", 13 | "core:menu:default", 14 | "core:tray:default", 15 | "shell:allow-open" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /example/src-tauri/gen/schemas/capabilities.json: -------------------------------------------------------------------------------- 1 | {"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default","shell:allow-open"]}} -------------------------------------------------------------------------------- /example/src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /example/src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /example/src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /example/src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /example/src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /example/src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /example/src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /example/src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /example/src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /example/src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /example/src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /example/src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /example/src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /example/src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /example/src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /example/src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /example/src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | use std::{sync::Arc, time::Duration}; 5 | use tauri::{ipc::Channel, AppHandle, EventTarget, Manager, Runtime, WebviewWindow, Window}; 6 | use taurpc::Router; 7 | use tokio::{ 8 | sync::{oneshot, Mutex}, 9 | time::sleep, 10 | }; 11 | 12 | #[doc = "Doc comments are also generated"] 13 | #[taurpc::ipc_type] 14 | // #[derive(serde::Serialize, serde::Deserialize, specta::Type, Clone)] 15 | struct User { 16 | /// The user's id 17 | uid: i32, 18 | /// The user's first name 19 | first_name: String, 20 | /// The user's last name 21 | last_name: String, 22 | } 23 | 24 | // create the error type that represents all errors possible in our program 25 | #[derive(Debug, thiserror::Error, specta::Type)] 26 | #[serde(tag = "type", content = "data")] 27 | enum Error { 28 | #[error(transparent)] 29 | Io( 30 | #[from] 31 | #[serde(skip)] 32 | std::io::Error, 33 | ), 34 | 35 | #[error("Other: `{0}`")] 36 | Other(String), 37 | } 38 | 39 | // we must manually implement serde::Serialize 40 | impl serde::Serialize for Error { 41 | fn serialize(&self, serializer: S) -> Result 42 | where 43 | S: serde::ser::Serializer, 44 | { 45 | serializer.serialize_str(self.to_string().as_ref()) 46 | } 47 | } 48 | 49 | #[taurpc::ipc_type] 50 | struct Update { 51 | progress: u8, 52 | } 53 | 54 | // #[taurpc::procedures(event_trigger = ApiEventTrigger)] 55 | #[taurpc::procedures(event_trigger = ApiEventTrigger, export_to = "../src/lib/bindings.ts")] 56 | trait Api { 57 | async fn update_state(app_handle: AppHandle, new_value: String); 58 | 59 | async fn get_window(window: Window); 60 | // async fn get_window(#[window] win: Window); 61 | 62 | async fn get_webview_window(webview_window: WebviewWindow); 63 | 64 | async fn get_app_handle(app_handle: AppHandle); 65 | // async fn get_app_handle(#[app_handle] ah: AppHandle); 66 | 67 | async fn test_io(_user: User) -> User; 68 | 69 | async fn test_option() -> Option<()>; 70 | 71 | async fn test_result(user: User) -> Result; 72 | 73 | // #[taurpc(skip)] 74 | async fn with_sleep(); 75 | 76 | #[taurpc(alias = "method_with_alias")] 77 | async fn with_alias(); 78 | 79 | #[taurpc(event)] 80 | async fn ev(updated_value: String); 81 | 82 | async fn vec_test(arg: Vec); 83 | 84 | async fn multiple_args(arg: Vec, arg2: String); 85 | 86 | async fn test_bigint(num: i64) -> i64; 87 | 88 | async fn with_channel(on_event: Channel); 89 | } 90 | 91 | #[derive(Clone)] 92 | struct ApiImpl { 93 | state: GlobalState, 94 | } 95 | 96 | #[taurpc::resolvers] 97 | impl Api for ApiImpl { 98 | async fn update_state(self, app_handle: AppHandle, new_value: String) { 99 | let mut data = self.state.lock().await; 100 | println!("Before {:?}", data); 101 | *data = new_value; 102 | println!("After {:?}", data); 103 | 104 | let uppercase = data.to_uppercase(); 105 | 106 | TauRpcEventsEventTrigger::new(app_handle) 107 | .state_changed(uppercase) 108 | .unwrap(); 109 | } 110 | 111 | async fn get_window(self, window: Window) { 112 | println!("Window: {}", window.label()); 113 | } 114 | 115 | async fn get_webview_window(self, webview_window: WebviewWindow) { 116 | println!("WebviewWindow: {}", webview_window.label()); 117 | } 118 | 119 | async fn get_app_handle(self, app_handle: AppHandle) { 120 | println!( 121 | "App Handle: {:?}, {:?}", 122 | app_handle.path().app_config_dir(), 123 | app_handle.package_info() 124 | ); 125 | } 126 | 127 | async fn test_io(self, user: User) -> User { 128 | user 129 | } 130 | 131 | async fn test_option(self) -> Option<()> { 132 | Some(()) 133 | } 134 | 135 | async fn test_result(self, user: User) -> Result { 136 | Err(Error::Other("Some error message".to_string())) 137 | // Ok(user) 138 | } 139 | 140 | async fn with_sleep(self) { 141 | sleep(Duration::from_millis(2000)).await; 142 | } 143 | 144 | async fn with_alias(self) { 145 | println!("method with alias called"); 146 | } 147 | 148 | async fn vec_test(self, _arg: Vec) {} 149 | 150 | async fn multiple_args(self, _arg: Vec, _arg2: String) {} 151 | 152 | async fn test_bigint(self, num: i64) -> i64 { 153 | num 154 | } 155 | 156 | async fn with_channel(self, on_event: Channel) { 157 | for progress in [15, 20, 35, 50, 90] { 158 | on_event.send(Update { progress }).unwrap(); 159 | } 160 | } 161 | } 162 | 163 | #[taurpc::procedures(path = "events", export_to = "../src/lib/bindings.ts")] 164 | trait Events { 165 | #[taurpc(event)] 166 | async fn test_ev(); 167 | 168 | #[taurpc(event)] 169 | async fn state_changed(new_state: String); 170 | 171 | #[taurpc(event)] 172 | async fn vec_test(args: Vec); 173 | 174 | #[taurpc(event)] 175 | async fn multiple_args(arg1: u16, arg2: Vec); 176 | } 177 | 178 | #[derive(Clone)] 179 | struct EventsImpl; 180 | 181 | #[taurpc::resolvers] 182 | impl Events for EventsImpl {} 183 | 184 | #[taurpc::procedures(path = "api.ui", export_to = "../src/lib/bindings.ts")] 185 | trait UiApi { 186 | async fn trigger(); 187 | 188 | #[taurpc(event)] 189 | async fn test_ev(); 190 | } 191 | 192 | #[derive(Clone)] 193 | struct UiApiImpl; 194 | 195 | #[taurpc::resolvers] 196 | impl UiApi for UiApiImpl { 197 | async fn trigger(self) { 198 | println!("Trigger ui event") 199 | } 200 | } 201 | 202 | type GlobalState = Arc>; 203 | 204 | #[tokio::main] 205 | async fn main() { 206 | let (tx, rx) = oneshot::channel::(); 207 | 208 | tokio::spawn(async move { 209 | let app_handle = rx.await.unwrap(); 210 | let events_trigger = TauRpcEventsEventTrigger::new(app_handle.clone()); 211 | let ui_trigger = TauRpcUiApiEventTrigger::new(app_handle); 212 | 213 | let mut interval = tokio::time::interval(Duration::from_secs(1)); 214 | loop { 215 | interval.tick().await; 216 | 217 | events_trigger.vec_test(vec![String::from("test"), String::from("test2")])?; 218 | 219 | events_trigger 220 | .send_to(EventTarget::Any) 221 | .vec_test(vec![String::from("test"), String::from("test2")])?; 222 | 223 | events_trigger.multiple_args(0, vec![String::from("test"), String::from("test2")])?; 224 | 225 | events_trigger.test_ev()?; 226 | ui_trigger.test_ev()?; 227 | } 228 | 229 | #[allow(unreachable_code)] 230 | Ok::<(), tauri::Error>(()) 231 | }); 232 | 233 | let router = Router::new() 234 | .export_config( 235 | specta_typescript::Typescript::default() 236 | .header("// My header\n\n") 237 | // Make sure prettier is installed before using this. 238 | .formatter(specta_typescript::formatter::prettier) 239 | .bigint(specta_typescript::BigIntExportBehavior::String), 240 | ) 241 | .merge( 242 | ApiImpl { 243 | state: Arc::new(Mutex::new("state".to_string())), 244 | } 245 | .into_handler(), 246 | ) 247 | .merge(EventsImpl.into_handler()) 248 | .merge(UiApiImpl.into_handler()); 249 | 250 | // Without router 251 | // tauri::Builder::default() 252 | // .invoke_handler(router.into_handler()) 253 | // // .invoke_handler(taurpc::create_ipc_handler( 254 | // // ApiImpl { 255 | // // state: Arc::new(Mutex::new("state".to_string())), 256 | // // } 257 | // // .into_handler(), 258 | // // )) 259 | // .setup(|app| { 260 | // tx.send(app.handle().clone()).unwrap(); 261 | // Ok(()) 262 | // }) 263 | // .run(tauri::generate_context!()) 264 | // .expect("error while running tauri application"); 265 | tauri::Builder::default() 266 | .plugin(tauri_plugin_shell::init()) 267 | .invoke_handler(router.into_handler()) 268 | .setup(|app| { 269 | #[cfg(debug_assertions)] 270 | app.get_webview_window("main").unwrap().open_devtools(); 271 | 272 | tx.send(app.handle().clone()).unwrap(); 273 | 274 | Ok(()) 275 | }) 276 | .run(tauri::generate_context!()) 277 | .expect("error while running tauri application"); 278 | } 279 | -------------------------------------------------------------------------------- /example/src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "productName": "example-v2-beta", 3 | "version": "0.0.0", 4 | "identifier": "example-taurpc", 5 | "build": { 6 | "beforeDevCommand": "pnpm dev", 7 | "devUrl": "http://localhost:1420", 8 | "beforeBuildCommand": "pnpm build", 9 | "frontendDist": "../dist" 10 | }, 11 | "app": { 12 | "windows": [ 13 | { 14 | "title": "example-v2-beta", 15 | "width": 800, 16 | "height": 600 17 | } 18 | ], 19 | "security": { 20 | "csp": null 21 | } 22 | }, 23 | "bundle": { 24 | "active": true, 25 | "targets": "all", 26 | "icon": [ 27 | "icons/32x32.png", 28 | "icons/128x128.png", 29 | "icons/128x128@2x.png", 30 | "icons/icon.icns", 31 | "icons/icon.ico" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/src/App.svelte: -------------------------------------------------------------------------------- 1 | 66 | 67 |
68 | Set managed state on backend 69 | 70 | 71 |
72 | Current State (uppercase): {state} 73 |
74 | -------------------------------------------------------------------------------- /example/src/lib/bindings.ts: -------------------------------------------------------------------------------- 1 | // My header 2 | 3 | // This file has been generated by Specta. DO NOT EDIT. 4 | 5 | import { 6 | createTauRPCProxy as createProxy, 7 | type InferCommandOutput, 8 | } from 'taurpc' 9 | type TAURI_CHANNEL = (response: T) => void 10 | 11 | export type Error = { type: 'Io' } | { type: 'Other'; data: string } 12 | 13 | export type Update = { progress: number } 14 | 15 | /** 16 | * Doc comments are also generated 17 | */ 18 | export type User = { 19 | /** 20 | * The user's id 21 | */ 22 | uid: number 23 | /** 24 | * The user's first name 25 | */ 26 | first_name: string 27 | /** 28 | * The user's last name 29 | */ 30 | last_name: string 31 | } 32 | 33 | const ARGS_MAP = { 34 | 'api.ui': '{"test_ev":[],"trigger":[]}', 35 | 'events': 36 | '{"vec_test":["args"],"multiple_args":["arg1","arg2"],"test_ev":[],"state_changed":["new_state"]}', 37 | '': 38 | '{"with_channel":["on_event"],"method_with_alias":[],"test_bigint":["num"],"test_option":[],"update_state":["new_value"],"get_app_handle":[],"get_webview_window":[],"test_io":["_user"],"ev":["updated_value"],"with_sleep":[],"test_result":["user"],"get_window":[],"vec_test":["arg"],"multiple_args":["arg","arg2"]}', 39 | } 40 | export type Router = { 41 | 'events': { 42 | test_ev: () => Promise 43 | state_changed: (newState: string) => Promise 44 | vec_test: (args: string[]) => Promise 45 | multiple_args: (arg1: number, arg2: string[]) => Promise 46 | } 47 | 'api.ui': { trigger: () => Promise; test_ev: () => Promise } 48 | '': { 49 | update_state: (newValue: string) => Promise 50 | get_window: () => Promise 51 | get_webview_window: () => Promise 52 | get_app_handle: () => Promise 53 | test_io: (user: User) => Promise 54 | test_option: () => Promise 55 | test_result: (user: User) => Promise 56 | with_sleep: () => Promise 57 | method_with_alias: () => Promise 58 | ev: (updatedValue: string) => Promise 59 | vec_test: (arg: string[]) => Promise 60 | multiple_args: (arg: string[], arg2: string) => Promise 61 | test_bigint: (num: string) => Promise 62 | with_channel: (onEvent: TAURI_CHANNEL) => Promise 63 | } 64 | } 65 | 66 | export type { InferCommandOutput } 67 | export const createTauRPCProxy = () => createProxy(ARGS_MAP) 68 | -------------------------------------------------------------------------------- /example/src/lib/ipc.ts: -------------------------------------------------------------------------------- 1 | export * from './bindings' 2 | -------------------------------------------------------------------------------- /example/src/main.ts: -------------------------------------------------------------------------------- 1 | import './styles.css' 2 | import App from './App.svelte' 3 | 4 | const app = new App({ 5 | target: document.getElementById('app'), 6 | }) 7 | 8 | export default app 9 | -------------------------------------------------------------------------------- /example/src/styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatsDK/TauRPC/b5e14c0691ffa9664af9bb88aa0862a086bc4f51/example/src/styles.css -------------------------------------------------------------------------------- /example/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /example/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | } 8 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "resolveJsonModule": true, 8 | /** 9 | * Typecheck JS in `.svelte` and `.js` files by default. 10 | * Disable checkJs if you'd like to use dynamic types in JS. 11 | * Note that setting allowJs false does not prevent the use 12 | * of JS in `.svelte` files. 13 | */ 14 | "allowJs": true, 15 | "checkJs": true, 16 | "isolatedModules": true 17 | }, 18 | "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], 19 | "references": [{ "path": "./tsconfig.node.json" }] 20 | } 21 | -------------------------------------------------------------------------------- /example/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler" 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /example/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { svelte } from '@sveltejs/vite-plugin-svelte' 2 | import { defineConfig } from 'vite' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig(async () => ({ 6 | plugins: [svelte()], 7 | 8 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` 9 | // 10 | // 1. prevent vite from obscuring rust errors 11 | clearScreen: false, 12 | // 2. tauri expects a fixed port, fail if that port is not available 13 | server: { 14 | port: 1420, 15 | strictPort: true, 16 | watch: { 17 | // 3. tell vite to ignore watching `src-tauri` 18 | ignored: ['**/src-tauri/**'], 19 | }, 20 | }, 21 | })) 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taurpc", 3 | "version": "1.8.1", 4 | "description": "", 5 | "main": "dist/index.cjs", 6 | "repository": { 7 | "url": "https://github.com/MatsDK/TauRPC", 8 | "type": "git" 9 | }, 10 | "type": "module", 11 | "exports": { 12 | "./package.json": "./package.json", 13 | ".": { 14 | "import": "./dist/index.js", 15 | "default": "./dist/index.cjs" 16 | } 17 | }, 18 | "types": "dist/index.d.ts", 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "build": "tsup", 24 | "dev": "tsx watch src/index.ts", 25 | "format": "dprint fmt", 26 | "lint": "run-p lint:*", 27 | "lint:format": "dprint check", 28 | "lint:types": "tsc", 29 | "lint:js": "eslint ." 30 | }, 31 | "keywords": [], 32 | "author": "MatsDK", 33 | "license": "ISC", 34 | "devDependencies": { 35 | "@changesets/changelog-github": "^0.4.8", 36 | "@changesets/cli": "^2.26.1", 37 | "@types/eslint": "^8.40.0", 38 | "@types/node": "^20.2.5", 39 | "@typescript-eslint/eslint-plugin": "^5.59.7", 40 | "@typescript-eslint/parser": "^5.59.7", 41 | "dprint": "^0.36.1", 42 | "eslint": "^8.41.0", 43 | "eslint-plugin-dprint": "^0.4.0", 44 | "npm-run-all": "^4.1.5", 45 | "tsup": "^6.7.0", 46 | "tsx": "^3.12.7", 47 | "typescript": "^5.0.4" 48 | }, 49 | "dependencies": { 50 | "@tauri-apps/api": "^2.0.2" 51 | }, 52 | "tsup": { 53 | "entry": [ 54 | "src/index.ts" 55 | ], 56 | "format": [ 57 | "esm", 58 | "cjs" 59 | ], 60 | "dts": { 61 | "resolve": true 62 | }, 63 | "external": [ 64 | ".taurpc" 65 | ], 66 | "splitting": true, 67 | "clean": true 68 | }, 69 | "packageManager": "pnpm@9.12.0" 70 | } 71 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Channel, invoke } from '@tauri-apps/api/core' 2 | import { type EventCallback, listen, UnlistenFn } from '@tauri-apps/api/event' 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | type RoutesLayer = { [key: string]: (...args: any) => unknown } 6 | type NestedRoutes = { 7 | [route: string]: RoutesLayer | NestedRoutes 8 | } 9 | type Router = NestedRoutes & { ''?: RoutesLayer } 10 | 11 | type InvokeFn< 12 | TRoutes extends RoutesLayer, 13 | TProc extends string, 14 | > = TRoutes[TProc] 15 | 16 | // Helper type to swap the return type of functions returning Promise to void 17 | type SwapReturnTypeToVoid = T extends (...args: infer A) => Promise 18 | ? (...args: A) => void 19 | : never 20 | 21 | type ListenerFn< 22 | TRoutes extends RoutesLayer, 23 | TProc extends string, 24 | > = SwapReturnTypeToVoid 25 | 26 | type InvokeLayer< 27 | TRoutes extends RoutesLayer, 28 | TProcedures extends Extract = Extract< 29 | keyof TRoutes, 30 | string 31 | >, 32 | > = { 33 | [TProc in TProcedures]: InvokeFn & { 34 | on: (listener: ListenerFn) => Promise 35 | } 36 | } 37 | 38 | type SplitKeyNested< 39 | TRouter extends NestedRoutes, 40 | TPath extends keyof TRouter, 41 | T extends string, 42 | > = T extends `${infer A}.${infer B}` 43 | ? { [K in A]: SplitKeyNested } 44 | : { 45 | [K in T]: TRouter[TPath] extends RoutesLayer ? InvokeLayer 46 | : never 47 | } 48 | 49 | type RouterPathsToNestedObject< 50 | TRouter extends NestedRoutes, 51 | TPath extends keyof TRouter, 52 | > = TPath extends `${infer A}.${infer B}` 53 | ? { [K in A]: SplitKeyNested } 54 | : { 55 | [K in TPath]: TRouter[TPath] extends RoutesLayer 56 | ? InvokeLayer 57 | : never 58 | } 59 | 60 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 61 | type UnionToIntersection = (U extends any ? (k: U) => void : never) extends 62 | ((k: infer I) => void) ? I : never 63 | 64 | type ConvertToNestedObject = UnionToIntersection< 65 | RouterPathsToNestedObject 66 | > 67 | 68 | type TauRpcProxy = 69 | & (TRouter[''] extends RoutesLayer ? InvokeLayer 70 | : object) 71 | & ConvertToNestedObject> 72 | 73 | type Payload = { 74 | event_name: string 75 | event: { proc_name: string; input_type: unknown } 76 | } 77 | type ListenFn = (args: unknown) => void 78 | type ArgsMap = Record> 79 | 80 | const TAURPC_EVENT_NAME = 'TauRpc_event' 81 | 82 | const createTauRPCProxy = ( 83 | args: Record, 84 | ) => { 85 | const args_map = parseArgsMap(args) 86 | return nestedProxy(args_map) as TauRpcProxy 87 | } 88 | 89 | const nestedProxy = ( 90 | args_maps: ArgsMap, 91 | path: string[] = [], 92 | ) => { 93 | return new window.Proxy({}, { 94 | get(_target, p, _receiver): object { 95 | const method_name = p.toString() 96 | const nested_path = [...path, method_name] 97 | const args_map = args_maps[path.join('.')] 98 | if (method_name === 'then') return {} 99 | 100 | if (args_map && method_name in args_map) { 101 | return new window.Proxy(() => { 102 | // Empty fn 103 | }, { 104 | get: (_target, prop, _receiver) => { 105 | if (prop !== 'on') return 106 | 107 | const event_name = nested_path.join('.') 108 | return async (listener: (args: unknown) => void) => { 109 | return await listen( 110 | TAURPC_EVENT_NAME, 111 | createEventHandlder(event_name, listener, args_map), 112 | ) 113 | } 114 | }, 115 | apply(_target, _thisArg, args) { 116 | return handleProxyCall( 117 | nested_path.join('.'), 118 | args, 119 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 120 | args_map[method_name]!, 121 | ) 122 | }, 123 | }) 124 | } else if ( 125 | nested_path.join('.') in args_maps 126 | || Object.keys(args_maps).some(path => 127 | path.startsWith(`${nested_path.join('.')}.`) 128 | ) 129 | ) { 130 | return nestedProxy(args_maps, nested_path) 131 | } else { 132 | throw new Error(`'${nested_path.join('.')}' not found`) 133 | } 134 | }, 135 | }) 136 | } 137 | 138 | const handleProxyCall = async ( 139 | path: string, 140 | args: unknown[], 141 | procedure_args: string[], 142 | ) => { 143 | const args_object: Record = {} 144 | 145 | for (let idx = 0; idx < procedure_args.length; idx++) { 146 | const arg_name = procedure_args[idx] 147 | if (!arg_name) throw new Error('Received invalid arguments') 148 | 149 | const arg = args[idx] 150 | if (typeof arg == 'function') { 151 | const channel = new Channel() 152 | channel.onmessage = arg as typeof channel.onmessage 153 | args_object[arg_name] = channel 154 | } else { 155 | args_object[arg_name] = arg 156 | } 157 | } 158 | 159 | const response = await invoke( 160 | `TauRPC__${path}`, 161 | args_object, 162 | ) 163 | return response 164 | } 165 | 166 | const createEventHandlder = ( 167 | event_name: string, 168 | listener: ListenFn, 169 | args_map: ArgsMap[string], 170 | ): EventCallback => { 171 | return (event) => { 172 | if (event_name !== event.payload.event_name) return 173 | 174 | const path_segments = event.payload.event_name.split('.') 175 | const ev = path_segments.pop() 176 | if (!ev) return 177 | 178 | const args = args_map[ev] 179 | if (!args) return 180 | 181 | if (args.length === 1) { 182 | listener(event.payload.event.input_type) 183 | } else if (Array.isArray(event.payload.event.input_type)) { 184 | const _ = (listener as ((...args: unknown[]) => void))( 185 | ...event.payload.event.input_type as unknown[], 186 | ) 187 | } else { 188 | listener(event.payload.event.input_type) 189 | } 190 | } 191 | } 192 | 193 | const parseArgsMap = (args: Record) => { 194 | const args_map: Record> = {} 195 | Object.entries(args).map( 196 | ([path, args]) => { 197 | args_map[path] = JSON.parse(args) as Record 198 | }, 199 | ) 200 | 201 | return args_map 202 | } 203 | 204 | export type InferCommandOutput< 205 | TRouter extends Router, 206 | TPath extends keyof TRouter, 207 | TCommand extends keyof TRouter[TPath], 208 | > = TRouter[TPath] extends RoutesLayer 209 | ? Awaited> 210 | : unknown 211 | 212 | export { createTauRPCProxy } 213 | -------------------------------------------------------------------------------- /taurpc/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ -------------------------------------------------------------------------------- /taurpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "taurpc" 3 | authors = ["MatsDK"] 4 | version = "0.5.0" 5 | edition = "2021" 6 | description = "A type-safe IPC layer for tauri commands" 7 | documentation = "https://docs.rs/taurpc" 8 | homepage = "https://github.com/MatsDK/TauRPC" 9 | repository = "https://github.com/MatsDK/TauRPC" 10 | license = "MIT OR Apache-2.0" 11 | readme = "../README.md" 12 | categories = [] 13 | rust = "1.71" 14 | 15 | [package.metadata."docs.rs"] 16 | all-features = true 17 | rustc-args = ["--cfg", "docsrs"] 18 | rustdoc-args = ["--cfg", "docsrs"] 19 | 20 | [dependencies] 21 | taurpc-macros = { path = "./taurpc-macros", version = "=0.5.0" } 22 | 23 | itertools = "0.13.0" 24 | tauri = { version = "2.2.5", features = ["specta"] } 25 | serde = { version = "1.0.215", features = ["derive"] } 26 | serde_json = "1.0.133" 27 | tokio = { version = "1", features = ["full"] } 28 | 29 | specta = { version = "=2.0.0-rc.22", features=["export", "function"] } 30 | specta-serde = { version = "0.0.9", features = [] } 31 | specta-typescript = { version = "0.0.9", features = ["function"] } 32 | specta-macros = { version = "2.0.0-rc.17" } 33 | heck = "0.5.0" 34 | anyhow = "1.0.95" 35 | 36 | [workspace] 37 | members = [ 38 | "taurpc-macros", 39 | ] 40 | -------------------------------------------------------------------------------- /taurpc/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 MatsDK 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /taurpc/src/export.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context, Result}; 2 | use heck::ToLowerCamelCase; 3 | use itertools::Itertools; 4 | use specta::datatype::{Function, FunctionResultVariant}; 5 | use specta::TypeCollection; 6 | use specta_typescript as ts; 7 | use specta_typescript::Typescript; 8 | use std::collections::HashMap; 9 | use std::fs::OpenOptions; 10 | use std::io::prelude::*; 11 | use std::path::Path; 12 | 13 | static PACKAGE_JSON: &str = r#" 14 | { 15 | "name": ".taurpc", 16 | "types": "index.ts" 17 | } 18 | "#; 19 | 20 | static BOILERPLATE_TS_IMPORT: &str = r#" 21 | 22 | import { createTauRPCProxy as createProxy, type InferCommandOutput } from 'taurpc' 23 | type TAURI_CHANNEL = (response: T) => void 24 | "#; 25 | 26 | static BOILERPLATE_TS_EXPORT: &str = r#" 27 | 28 | export type { InferCommandOutput } 29 | export const createTauRPCProxy = () => createProxy(ARGS_MAP) 30 | "#; 31 | 32 | /// Export the generated TS types with the code necessary for generating the client proxy. 33 | /// 34 | /// By default, if the `export_to` attribute was not specified on the procedures macro, it will be exported 35 | /// to `node_modules/.taurpc` and a `package.json` will also be generated to import the package. 36 | /// Otherwise the code will just be export to the .ts file specified by the user. 37 | pub(super) fn export_types( 38 | export_path: Option<&'static str>, 39 | args_map: HashMap, 40 | export_config: ts::Typescript, 41 | functions: HashMap>, 42 | mut type_map: TypeCollection, 43 | ) -> Result<()> { 44 | let export_path = export_path.map(|p| p.to_string()).unwrap_or( 45 | std::env::current_dir() 46 | .unwrap() 47 | .join("../bindings.ts") 48 | .into_os_string() 49 | .into_string() 50 | .unwrap(), 51 | ); 52 | let path = Path::new(&export_path); 53 | 54 | if path.is_dir() || !export_path.ends_with(".ts") { 55 | bail!("`export_to` path should be a ts file"); 56 | } 57 | 58 | if let Some(parent) = path.parent() { 59 | std::fs::create_dir_all(parent) 60 | .context("Failed to create directory for exported bindings")?; 61 | } 62 | 63 | // Export `types_map` containing all referenced types. 64 | type_map.remove( as specta::NamedType>::sid()); 65 | let types = export_config 66 | .export(&type_map) 67 | .context("Failed to generate types with specta")?; 68 | 69 | // Put headers always at the top of the file, followed by the module imports. 70 | let framework_header = export_config.framework_header.as_ref(); 71 | let body = types.split_once(framework_header).unwrap().1; 72 | 73 | let mut file = OpenOptions::new() 74 | .create(true) 75 | .truncate(true) 76 | .write(true) 77 | .open(path) 78 | .context("Cannot open bindings file")?; 79 | 80 | file.write_all(export_config.header.as_bytes()).unwrap(); 81 | file.write_all(framework_header.as_bytes()).unwrap(); 82 | file.write_all(BOILERPLATE_TS_IMPORT.as_bytes()).unwrap(); 83 | file.write_all(body.as_bytes()).unwrap(); 84 | 85 | let args_entries: String = args_map 86 | .iter() 87 | .map(|(k, v)| format!("'{k}':'{v}'")) 88 | .join(", "); 89 | let router_args = format!("{{ {args_entries} }}"); 90 | 91 | file.write_all(format!("const ARGS_MAP = {router_args}\n").as_bytes()) 92 | .unwrap(); 93 | file.write_all( 94 | generate_functions_router(functions, type_map, &export_config) 95 | .unwrap() 96 | .as_bytes(), 97 | ) 98 | .unwrap(); 99 | file.write_all(BOILERPLATE_TS_EXPORT.as_bytes()).unwrap(); 100 | 101 | if export_path.ends_with("node_modules\\.taurpc\\index.ts") { 102 | let package_json_path = Path::new(&export_path) 103 | .parent() 104 | .map(|path| path.join("package.json")) 105 | .context("Failed to create 'package.json' path")?; 106 | 107 | std::fs::write(package_json_path, PACKAGE_JSON) 108 | .context("failed to create 'package.json'")?; 109 | } 110 | 111 | // Format the output file if the user specified a formatter on `export_config`. 112 | export_config.format(path).context( 113 | "Failed to format exported bindings, make sure you have the correct formatter installed", 114 | )?; 115 | Ok(()) 116 | } 117 | 118 | fn generate_functions_router( 119 | functions: HashMap>, 120 | type_map: TypeCollection, 121 | export_config: &Typescript, 122 | ) -> Result { 123 | let functions = functions 124 | .iter() 125 | .map(|(path, functions)| { 126 | let functions = functions 127 | .iter() 128 | .map(|function| generate_function(function, export_config, &type_map)) 129 | .collect::, _>>() 130 | .unwrap() 131 | .join(", \n"); 132 | 133 | format!("'{path}': {{ {functions} }}") 134 | }) 135 | .collect::>() 136 | .join(",\n"); 137 | 138 | Ok(format!("export type Router = {{ {functions} }};\n")) 139 | } 140 | 141 | fn generate_function( 142 | function: &Function, 143 | export_config: &Typescript, 144 | type_map: &TypeCollection, 145 | ) -> Result { 146 | let args = function 147 | .args() 148 | .map(|(name, typ)| { 149 | ts::datatype( 150 | export_config, 151 | &FunctionResultVariant::Value(typ.clone()), 152 | type_map, 153 | ) 154 | .map(|ty| format!("{}: {}", name.to_lower_camel_case(), ty)) 155 | }) 156 | .collect::, _>>() 157 | .context("An error occured while generating command args")? 158 | .join(", "); 159 | 160 | let return_ty = match function.result() { 161 | Some(FunctionResultVariant::Value(t)) => ts::datatype( 162 | export_config, 163 | &FunctionResultVariant::Value(t.clone()), 164 | type_map, 165 | )?, 166 | // TODO: handle result types 167 | Some(FunctionResultVariant::Result(t, _e)) => ts::datatype( 168 | export_config, 169 | &FunctionResultVariant::Value(t.clone()), 170 | type_map, 171 | )?, 172 | None => "void".to_string(), 173 | }; 174 | 175 | let name = function.name().split_once("_taurpc_fn__").unwrap().1; 176 | Ok(format!(r#"{name}: ({args}) => Promise<{return_ty}>"#)) 177 | } 178 | -------------------------------------------------------------------------------- /taurpc/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides a typesafe IPC layer for Tauri's commands and events. 2 | //! TauRPC should be used instead of [Tauri's IPC system](https://v2.tauri.app/develop/calling-rust), 3 | //! which does not provide TypeScript types for your commands or events. 4 | //! 5 | //! Go the the [GitHub](https://github.com/MatsDK/TauRPC/#readme) page to get started. 6 | 7 | pub extern crate serde; 8 | pub extern crate specta; 9 | pub extern crate specta_macros; 10 | use specta::datatype::Function; 11 | use specta::TypeCollection; 12 | pub use specta_typescript::Typescript; 13 | 14 | use std::{collections::HashMap, fmt::Debug, sync::Arc}; 15 | use tokio::sync::broadcast::Sender; 16 | 17 | use serde::Serialize; 18 | use tauri::ipc::{Invoke, InvokeError}; 19 | use tauri::{AppHandle, Emitter, EventTarget, Runtime}; 20 | 21 | pub use taurpc_macros::{ipc_type, procedures, resolvers}; 22 | 23 | mod export; 24 | use export::export_types; 25 | 26 | /// A trait, which is automatically implemented by `#[taurpc::procedures]`, that is used for handling incoming requests 27 | /// and the type generation. 28 | pub trait TauRpcHandler: Sized { 29 | const TRAIT_NAME: &'static str; 30 | 31 | /// This handler's prefix in the TypeScript router. 32 | const PATH_PREFIX: &'static str; 33 | 34 | /// Bindings export path optionally specified by the user. 35 | const EXPORT_PATH: Option<&'static str>; 36 | 37 | /// Handle a single incoming request 38 | fn handle_incoming_request(self, invoke: Invoke); 39 | 40 | /// Spawn a new `tokio` thread that listens for and handles incoming request through a `tokio::broadcast::channel`. 41 | /// This is used for when you have multiple handlers inside a router. 42 | fn spawn(self) -> Sender>>; 43 | 44 | /// Returns a json object containing the arguments for the methods. 45 | /// This is used on the frontend to ensure the arguments are send with their correct idents to the backend. 46 | fn args_map() -> String; 47 | 48 | /// Returns all of the functions for exporting, all referenced types will be added to `type_map`. 49 | fn collect_fn_types(type_map: &mut TypeCollection) -> Vec; 50 | } 51 | 52 | /// Creates a handler that allows your IPCs to be called from the frontend with the coresponding 53 | /// types. Accepts a struct in which your `taurpc::procedures` trait is implemented. 54 | /// If you have nested routes, look at [taurpc::Router](https://docs.rs/taurpc/latest/taurpc/struct.Router.html). 55 | /// 56 | /// 57 | /// # Examples 58 | /// ```rust 59 | /// #[taurpc::procedures] 60 | /// trait Api { 61 | /// async fn hello_world(); 62 | /// } 63 | /// 64 | /// #[derive(Clone)] 65 | /// struct ApiImpl; 66 | /// #[taurpc::resolvers] 67 | /// impl Api for ApiImpl { 68 | /// async fn hello_world(self) { 69 | /// println!("Hello world"); 70 | /// } 71 | /// } 72 | /// 73 | /// #[tokio::main] 74 | /// async fn main() { 75 | /// tauri::Builder::default() 76 | /// .invoke_handler( 77 | /// taurpc::create_ipc_handler(ApiImpl.into_handler()); 78 | /// ) 79 | /// .run(tauri::generate_context!()) 80 | /// .expect("error while running tauri application"); 81 | /// } 82 | /// ``` 83 | pub fn create_ipc_handler( 84 | procedures: H, 85 | ) -> impl Fn(Invoke) -> bool + Send + Sync + 'static 86 | where 87 | H: TauRpcHandler + Send + Sync + 'static + Clone, 88 | { 89 | let args_map = HashMap::from([(H::PATH_PREFIX.to_string(), H::args_map())]); 90 | let mut type_map = TypeCollection::default(); 91 | let functions = HashMap::from([( 92 | H::PATH_PREFIX.to_string(), 93 | H::collect_fn_types(&mut type_map), 94 | )]); 95 | #[cfg(debug_assertions)] // Only export in development builds 96 | export_types( 97 | H::EXPORT_PATH, 98 | args_map, 99 | specta_typescript::Typescript::default(), 100 | functions, 101 | type_map, 102 | ) 103 | .unwrap(); 104 | move |invoke: Invoke| { 105 | procedures.clone().handle_incoming_request(invoke); 106 | true 107 | } 108 | } 109 | 110 | #[derive(Serialize, Clone)] 111 | struct Event { 112 | event: S, 113 | event_name: String, 114 | } 115 | 116 | /// A structure used for triggering [tauri events](https://v2.tauri.app/develop/calling-rust/#accessing-the-webviewwindow-in-commands) on the frontend. 117 | /// By default the events are send to all windows with `emit_all`, if you want to send to a specific window by label, 118 | /// use `new_scoped` or `new_scoped_from_trigger`. 119 | #[derive(Debug)] 120 | pub struct EventTrigger { 121 | app_handle: AppHandle, 122 | path_prefix: String, 123 | target: EventTarget, 124 | } 125 | 126 | impl Clone for EventTrigger { 127 | fn clone(&self) -> Self { 128 | Self { 129 | app_handle: self.app_handle.clone(), 130 | path_prefix: self.path_prefix.clone(), 131 | target: self.target.clone(), 132 | } 133 | } 134 | } 135 | 136 | impl EventTrigger { 137 | pub fn new(app_handle: AppHandle, path_prefix: String) -> Self { 138 | Self { 139 | app_handle, 140 | path_prefix, 141 | target: EventTarget::Any, 142 | } 143 | } 144 | 145 | pub fn new_scoped>( 146 | app_handle: AppHandle, 147 | path_prefix: String, 148 | target: I, 149 | ) -> Self { 150 | Self { 151 | app_handle, 152 | path_prefix, 153 | target: target.into(), 154 | } 155 | } 156 | 157 | pub fn new_scoped_from_trigger(trigger: Self, target: EventTarget) -> Self { 158 | Self { 159 | app_handle: trigger.app_handle, 160 | path_prefix: trigger.path_prefix, 161 | target, 162 | } 163 | } 164 | 165 | pub fn call(&self, proc_name: &str, event: S) -> tauri::Result<()> { 166 | let event_name = if self.path_prefix.is_empty() { 167 | proc_name.to_string() 168 | } else { 169 | format!("{}.{}", self.path_prefix, proc_name) 170 | }; 171 | let event = Event { event_name, event }; 172 | let _ = self 173 | .app_handle 174 | .emit_to(self.target.clone(), "TauRpc_event", event); 175 | Ok(()) 176 | } 177 | } 178 | 179 | /// Used for merging nested trait implementations. This is used when you have multiple trait implementations, 180 | /// instead of `taurpc::create_ipc_handler()`. Use `.merge()` to add trait implementations to the router. 181 | /// The trait must have the `#[taurpc::procedures]` macro and the nested routes should have `#[taurpc::procedures(path = "path")]`. 182 | /// 183 | /// # Examples 184 | /// ```rust 185 | /// #[taurpc::procedures] 186 | /// trait Api { } 187 | /// 188 | /// #[derive(Clone)] 189 | /// struct ApiImpl; 190 | /// 191 | /// #[taurpc::resolveres] 192 | /// impl Api for ApiImpl { } 193 | /// 194 | /// #[taurpc::procedures(path = "events")] 195 | /// trait Events { } 196 | /// 197 | /// #[derive(Clone)] 198 | /// struct EventsImpl; 199 | /// 200 | /// #[taurpc::resolveres] 201 | /// impl Events for EventsImpl { } 202 | /// 203 | /// #[tokio::main] 204 | /// async fn main() { 205 | /// let router = Router::new() 206 | /// .merge(ApiImpl.into_handler()) 207 | /// .merge(EventsImpl.into_handler()); 208 | /// 209 | /// tauri::Builder::default() 210 | /// .invoke_handler(router.into_handler()) 211 | /// .run(tauri::generate_context!()) 212 | /// .expect("error while running tauri application"); 213 | /// } 214 | /// ``` 215 | #[derive(Default)] 216 | pub struct Router { 217 | types: TypeCollection, 218 | handlers: HashMap>>>, 219 | export_path: Option<&'static str>, 220 | args_map_json: HashMap, 221 | fns_map: HashMap>, 222 | export_config: specta_typescript::Typescript, 223 | } 224 | 225 | impl Router { 226 | pub fn new() -> Self { 227 | Self { 228 | types: TypeCollection::default(), 229 | handlers: HashMap::new(), 230 | fns_map: HashMap::new(), 231 | export_path: None, 232 | args_map_json: HashMap::new(), 233 | export_config: specta_typescript::Typescript::default(), 234 | } 235 | } 236 | 237 | /// Overwrite `specta`'s default TypeScript export options, look at the docs for 238 | /// `specta_typescript::Typescript` for all the configuration options. 239 | /// 240 | /// Example: 241 | /// ```rust 242 | /// let router = Router::new() 243 | /// .export_config( 244 | /// specta_typescript::Typescript::default() 245 | /// .header("// My header\n") 246 | /// .bigint(specta_typescript::BigIntExportBehavior::String), 247 | /// ) 248 | /// .merge(...); 249 | /// ``` 250 | pub fn export_config(mut self, config: specta_typescript::Typescript) -> Self { 251 | self.export_config = config; 252 | self 253 | } 254 | 255 | /// Add routes to the router, accepts a struct for which a `#[taurpc::procedures]` trait is implemented 256 | /// 257 | /// ```rust 258 | /// let router = Router::new() 259 | /// .merge(ApiImpl.into_handler()) 260 | /// .merge(EventsImpl.into_handler()); 261 | /// ``` 262 | pub fn merge>(mut self, handler: H) -> Self { 263 | if let Some(path) = H::EXPORT_PATH { 264 | self.export_path = Some(path) 265 | } 266 | 267 | self.args_map_json 268 | .insert(H::PATH_PREFIX.to_string(), H::args_map()); 269 | self.fns_map.insert( 270 | H::PATH_PREFIX.to_string(), 271 | H::collect_fn_types(&mut self.types), 272 | ); 273 | self.handlers 274 | .insert(H::PATH_PREFIX.to_string(), handler.spawn()); 275 | self 276 | } 277 | 278 | /// Create a handler out of the router that allows your IPCs to be called from the frontend, 279 | /// and generate the corresponding types. Use this inside `.invoke_handler()` on the tauri::Builder. 280 | /// 281 | /// ```rust 282 | /// tauri::Builder::default() 283 | /// .invoke_handler(router.into_handler()) 284 | /// .run(tauri::generate_context!()) 285 | /// .expect("error while running tauri application"); 286 | /// ``` 287 | pub fn into_handler(self) -> impl Fn(Invoke) -> bool { 288 | #[cfg(debug_assertions)] // Only export in development builds 289 | export_types( 290 | self.export_path, 291 | self.args_map_json.clone(), 292 | self.export_config.clone(), 293 | self.fns_map.clone(), 294 | self.types.clone(), 295 | ) 296 | .unwrap(); 297 | 298 | move |invoke: Invoke| self.on_command(invoke) 299 | } 300 | 301 | fn on_command(&self, invoke: Invoke) -> bool { 302 | let cmd = invoke.message.command(); 303 | if !cmd.starts_with("TauRPC__") { 304 | return false; 305 | } 306 | 307 | // Remove `TauRPC__` 308 | let prefix = cmd[8..].to_string(); 309 | let mut prefix = prefix.split('.').collect::>(); 310 | // Remove the actual name of the command 311 | prefix.pop().unwrap(); 312 | 313 | match self.handlers.get(&prefix.join(".")) { 314 | Some(handler) => { 315 | let _ = handler.send(Arc::new(invoke)); 316 | } 317 | None => invoke 318 | .resolver 319 | .invoke_error(InvokeError::from(format!("`{cmd}` not found"))), 320 | }; 321 | 322 | true 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /taurpc/taurpc-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "taurpc-macros" 3 | version = "0.5.0" 4 | edition = "2021" 5 | description = "Macros for the taurpc crate" 6 | documentation = "https://docs.rs/taurpc" 7 | homepage = "https://github.com/MatsDK/TauRPC" 8 | repository = "https://github.com/MatsDK/TauRPC" 9 | license = "MIT OR Apache-2.0" 10 | readme = "README.md" 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | syn = { version = "2.0.15", features = ["full"] } 17 | quote = "1.0.26" 18 | proc-macro2 = "1.0.56" 19 | serde = { version = "1", features = ["derive"] } 20 | 21 | serde_json = "1.0.96" 22 | # convert_case = "0.6" 23 | -------------------------------------------------------------------------------- /taurpc/taurpc-macros/README.md: -------------------------------------------------------------------------------- 1 | # taurpc-macros 2 | 3 | Crate containing the macros used for [taurpc](https://github.com/MatsDK/TauRPC) APIs. These are used for generating the TS types on runtime. 4 | -------------------------------------------------------------------------------- /taurpc/taurpc-macros/src/args.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream as TokenStream2; 2 | use quote::{quote, ToTokens}; 3 | use syn::{ext::IdentExt, spanned::Spanned, Ident, Pat, PatType, Type}; 4 | 5 | // TODO: Add raw request?? 6 | const RESERVED_ARGS: &[&str] = &["window", "state", "app_handle", "webview_window"]; 7 | 8 | pub(crate) struct Arg { 9 | pat: PatType, 10 | /// Should this argument be skipped in the generated types. 11 | pub skip_type: bool, 12 | // alias: String 13 | } 14 | 15 | impl Arg { 16 | pub fn ty(&self) -> &Type { 17 | &self.pat.ty 18 | } 19 | 20 | pub fn pat(&self) -> &Pat { 21 | &self.pat.pat 22 | } 23 | } 24 | 25 | impl From for Arg { 26 | fn from(mut pat: PatType) -> Self { 27 | // Skip this argument in type generation based on our defined reserved argument names. 28 | let mut skip_type = matches!( 29 | pat.pat.as_ref(), 30 | Pat::Ident(pat_ident) if RESERVED_ARGS.iter().any(|&s| pat_ident.ident == s) 31 | ); 32 | 33 | // These reserved args can also be used when they are tagged with an attribute, for 34 | // example `fn my_command(#[app_handle] h: AppHandle)`. 35 | pat.attrs = pat 36 | .attrs 37 | .into_iter() 38 | .filter(|attr| { 39 | if RESERVED_ARGS.iter().any(|s| attr.path().is_ident(s)) { 40 | skip_type = true; 41 | return false; 42 | } 43 | 44 | true 45 | }) 46 | .collect::>(); 47 | 48 | Self { pat, skip_type } 49 | } 50 | } 51 | 52 | impl ToTokens for Arg { 53 | fn to_tokens(&self, tokens: &mut TokenStream2) { 54 | self.pat.to_tokens(tokens); 55 | } 56 | } 57 | 58 | /// Generate the code that extracts and deserializes the args from the tauri message. 59 | pub(crate) fn parse_args( 60 | args: &[Arg], 61 | message: &Ident, 62 | proc_ident: &Ident, 63 | ) -> syn::Result> { 64 | args.iter() 65 | .map(|arg| parse_arg(arg, message, proc_ident)) 66 | .collect() 67 | } 68 | 69 | fn parse_arg(arg: &Arg, message: &Ident, proc_ident: &Ident) -> syn::Result { 70 | let key = parse_arg_key(arg)?; 71 | 72 | // catch self arguments that use FnArg::Typed syntax 73 | if key == "self" { 74 | return Err(syn::Error::new( 75 | key.span(), 76 | "unable to use self as a command function parameter", 77 | )); 78 | } 79 | 80 | // this way tauri knows how to deserialize the different types of the args 81 | Ok(quote!(::tauri::ipc::CommandArg::from_command( 82 | ::tauri::ipc::CommandItem { 83 | name: stringify!(#proc_ident), 84 | key: #key, 85 | message: &#message, 86 | acl: &None, 87 | plugin: None, 88 | } 89 | ))) 90 | } 91 | 92 | pub(crate) fn parse_arg_key(arg: &Arg) -> Result { 93 | // we only support patterns that allow us to extract some sort of keyed identifier 94 | match arg.pat() { 95 | Pat::Ident(arg) => Ok(arg.ident.unraw().to_string()), 96 | Pat::Wild(_) => Ok("".into()), // we always convert to camelCase, so "_" will end up empty anyways 97 | Pat::Struct(s) => Ok(s.path.segments.last().unwrap().ident.to_string()), 98 | Pat::TupleStruct(s) => Ok(s.path.segments.last().unwrap().ident.to_string()), 99 | err => Err(syn::Error::new( 100 | err.span(), 101 | "only named, wildcard, struct, and tuple struct arguments allowed", 102 | )), 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /taurpc/taurpc-macros/src/attrs.rs: -------------------------------------------------------------------------------- 1 | use super::extend_errors; 2 | use proc_macro2::Ident; 3 | use syn::{ 4 | parse::{Parse, ParseStream}, 5 | spanned::Spanned, 6 | Attribute, Expr, Lit, LitStr, MetaNameValue, Token, 7 | }; 8 | 9 | /// Attributes added on the procedures trait itself, `#[taurpc::procedures( ... )]`. 10 | #[derive(Debug, Default)] 11 | pub struct ProceduresAttrs { 12 | pub event_trigger_ident: Option, 13 | pub export_to: Option, 14 | pub path: String, 15 | } 16 | 17 | impl Parse for ProceduresAttrs { 18 | fn parse(input: ParseStream) -> syn::Result { 19 | let meta_items = input.parse_terminated(MetaNameValue::parse, Token![,])?; 20 | 21 | let mut errors = Ok(()); 22 | let mut result = Self::default(); 23 | 24 | for meta in meta_items { 25 | if meta.path.segments.len() != 1 { 26 | extend_errors!( 27 | errors, 28 | syn::Error::new( 29 | meta.span(), 30 | "taurpc::procedures does not support this meta item" 31 | ) 32 | ); 33 | } 34 | 35 | if meta.path.is_ident("event_trigger") { 36 | if let Expr::Path(p) = meta.value { 37 | if p.path.segments.len() != 1 { 38 | extend_errors!( 39 | errors, 40 | syn::Error::new( 41 | p.span(), 42 | "taurpc::procedures does not support this meta value" 43 | ) 44 | ); 45 | } 46 | 47 | let ident = p.path.get_ident().unwrap(); 48 | result.event_trigger_ident = Some(ident.clone()); 49 | } 50 | } else if meta.path.is_ident("export_to") { 51 | if let Expr::Lit(p) = meta.value { 52 | match p.lit { 53 | Lit::Str(str) => result.export_to = Some(str.value()), 54 | _ => { 55 | extend_errors!( 56 | errors, 57 | syn::Error::new(p.span(), "export_to should be a str") 58 | ); 59 | } 60 | } 61 | } else { 62 | extend_errors!( 63 | errors, 64 | syn::Error::new(meta.path.span(), "export_to should be a str") 65 | ); 66 | } 67 | } else if meta.path.is_ident("path") { 68 | if let Expr::Lit(p) = meta.value { 69 | match p.lit { 70 | Lit::Str(str) => { 71 | // TODO: validate path 72 | result.path = str.value() 73 | } 74 | _ => { 75 | extend_errors!( 76 | errors, 77 | syn::Error::new(p.span(), "path should be a str") 78 | ); 79 | } 80 | } 81 | } else { 82 | extend_errors!( 83 | errors, 84 | syn::Error::new(meta.path.span(), "path should be a str") 85 | ); 86 | } 87 | } else { 88 | extend_errors!( 89 | errors, 90 | syn::Error::new(meta.path.span(), "Unsupported attribute") 91 | ); 92 | } 93 | } 94 | 95 | errors?; 96 | 97 | Ok(result) 98 | } 99 | } 100 | 101 | /// Attributes defined on methods inside a procedures trait. 102 | /// Parse the attributes to make sure they are defined in the correct way, like `#[taurpc( ... )]`, accumulate 103 | /// all errors and then display them together with `extend_errors!()`. 104 | #[derive(Default, Debug)] 105 | pub struct MethodAttrs { 106 | pub(crate) skip: bool, 107 | pub(crate) alias: Option, 108 | pub(crate) is_event: bool, 109 | } 110 | 111 | impl Parse for MethodAttrs { 112 | fn parse(input: ParseStream) -> syn::Result { 113 | let mut res = MethodAttrs::default(); 114 | let attrs = input.call(Attribute::parse_outer)?; 115 | 116 | let mut errors = Ok(()); 117 | 118 | for attr in attrs { 119 | if !attr.path().is_ident("taurpc") { 120 | extend_errors!( 121 | errors, 122 | syn::Error::new( 123 | attr.meta.span(), 124 | "these attributes are not supported, use `#[taurpc(...)]` instead" 125 | ) 126 | ); 127 | // continue; 128 | } 129 | 130 | if let Err(e) = attr.parse_nested_meta(|meta| { 131 | if meta.path.is_ident("skip") { 132 | res.skip = true; 133 | Ok(()) 134 | } else if meta.path.is_ident("event") { 135 | res.is_event = true; 136 | Ok(()) 137 | } else if meta.path.is_ident("alias") { 138 | let value = meta.value()?; 139 | let alias: LitStr = value.parse()?; 140 | 141 | res.alias = Some(alias.value()); 142 | Ok(()) 143 | } else { 144 | Err(meta.error("unsupported attribute")) 145 | } 146 | }) { 147 | extend_errors!(errors, e); 148 | }; 149 | } 150 | 151 | errors?; 152 | 153 | Ok(res) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /taurpc/taurpc-macros/src/generator.rs: -------------------------------------------------------------------------------- 1 | use crate::args::{parse_arg_key, parse_args}; 2 | use crate::{method_fut_ident, proc::IpcMethod}; 3 | 4 | use proc_macro2::TokenStream as TokenStream2; 5 | use quote::{format_ident, quote, ToTokens}; 6 | use std::collections::HashMap; 7 | use syn::{parse_quote, Attribute, Generics, Ident, Type, Visibility}; 8 | 9 | pub struct ProceduresGenerator<'a> { 10 | pub trait_ident: &'a Ident, 11 | pub handler_ident: &'a Ident, 12 | pub event_trigger_ident: &'a Ident, 13 | pub export_path: Option, 14 | pub path_prefix: String, 15 | pub inputs_ident: &'a Ident, 16 | pub outputs_ident: &'a Ident, 17 | pub output_futures_ident: &'a Ident, 18 | pub vis: &'a Visibility, 19 | pub generics: &'a Generics, 20 | pub attrs: &'a [Attribute], 21 | pub methods: &'a [IpcMethod], 22 | pub method_output_types: &'a [&'a Type], 23 | pub alias_method_idents: &'a [Ident], 24 | } 25 | 26 | impl ProceduresGenerator<'_> { 27 | fn procedures_trait(&self) -> TokenStream2 { 28 | let &ProceduresGenerator { 29 | trait_ident, 30 | handler_ident, 31 | methods, 32 | vis, 33 | generics, 34 | attrs, 35 | method_output_types, 36 | alias_method_idents, 37 | .. 38 | } = self; 39 | 40 | let fn_types = alias_method_idents.iter().zip(methods).map( 41 | |(ident, IpcMethod { output, args, .. })| { 42 | let args = args.iter().filter(|&arg| !arg.skip_type); 43 | let fn_ident = fn_ident(trait_ident, ident); 44 | 45 | quote! { 46 | #[specta::specta] 47 | #[allow(non_snake_case, unused_variables)] 48 | fn #fn_ident( #( #args ),*) #output { 49 | unimplemented!(); 50 | } 51 | } 52 | }, 53 | ); 54 | 55 | let types_and_fns = methods.iter().zip(method_output_types.iter()).filter_map( 56 | |( 57 | IpcMethod { 58 | ident, 59 | args, 60 | generics, 61 | attrs, 62 | .. 63 | }, 64 | output_ty, 65 | )| { 66 | // skip methods that are marked as events, these methods don't need an implementation 67 | if attrs.is_event { 68 | return None; 69 | } 70 | let ty_doc = format!("The response future returned by [`{trait_ident}::{ident}`]."); 71 | let future_type_ident = method_fut_ident(ident); 72 | 73 | Some(quote! { 74 | #[allow(non_camel_case_types)] 75 | #[doc = #ty_doc] 76 | type #future_type_ident: std::future::Future + Send; 77 | 78 | fn #ident #generics(self, #( #args ),*) -> Self::#future_type_ident; 79 | }) 80 | }, 81 | ); 82 | 83 | quote! { 84 | #( #attrs )* 85 | #vis trait #trait_ident #generics: Sized { 86 | #( #types_and_fns )* 87 | 88 | /// Returns handler used for incoming requests and type generation. 89 | fn into_handler(self) -> #handler_ident { 90 | #handler_ident { methods: self } 91 | } 92 | } 93 | 94 | #( #fn_types )* 95 | } 96 | } 97 | 98 | fn input_enum(&self) -> TokenStream2 { 99 | let &Self { 100 | methods, 101 | vis, 102 | inputs_ident, 103 | alias_method_idents, 104 | .. 105 | } = self; 106 | 107 | let inputs = 108 | alias_method_idents 109 | .iter() 110 | .zip(methods) 111 | .map(|(ident, IpcMethod { args, .. })| { 112 | // Filter out Tauri's reserved arguments (state, window, app_handle). 113 | let types = args 114 | .iter() 115 | .filter(|&arg| !arg.skip_type) 116 | .map(|arg| arg.ty()) 117 | .collect::>(); 118 | 119 | // Tuples with 1 element were parsed as Type::Paren, which is not supported by specta. 120 | // This may not be necessary and there is probably a better solution, but this works. 121 | let ty: Type = if types.len() == 1 { 122 | let t = types[0]; 123 | parse_quote! {#t} 124 | } else { 125 | parse_quote! { 126 | ( #( #types ),* ) 127 | } 128 | }; 129 | quote! { 130 | #ident(#ty) 131 | } 132 | }); 133 | 134 | quote! { 135 | #[derive(taurpc::serde::Serialize, Clone)] 136 | #[serde(tag = "proc_name", content = "input_type")] 137 | #[allow(non_camel_case_types)] 138 | #vis enum #inputs_ident { 139 | #( #inputs ),* 140 | } 141 | } 142 | } 143 | 144 | fn output_enum(&self) -> TokenStream2 { 145 | let &Self { 146 | methods, 147 | vis, 148 | outputs_ident, 149 | method_output_types, 150 | .. 151 | } = self; 152 | 153 | let outputs = methods.iter().zip(method_output_types.iter()).map( 154 | |(IpcMethod { ident, .. }, output_ty)| { 155 | quote! { 156 | #ident(#output_ty) 157 | } 158 | }, 159 | ); 160 | 161 | quote! { 162 | #[derive(taurpc::serde::Serialize)] 163 | #[serde(tag = "proc_name", content = "output_type")] 164 | #[allow(non_camel_case_types)] 165 | #vis enum #outputs_ident { 166 | #( #outputs ),* 167 | } 168 | } 169 | } 170 | 171 | fn output_futures(&self) -> TokenStream2 { 172 | let &Self { 173 | methods, 174 | trait_ident, 175 | vis, 176 | output_futures_ident, 177 | outputs_ident, 178 | .. 179 | } = self; 180 | 181 | let outputs = methods 182 | .iter() 183 | .filter_map(|IpcMethod { ident, attrs, .. }| { 184 | if attrs.is_event { 185 | return None; 186 | } 187 | let future_ident = method_fut_ident(ident); 188 | 189 | Some(quote! { 190 | #ident(

::#future_ident) 191 | }) 192 | }) 193 | .collect::>(); 194 | 195 | // If there are no commands, there are no future outputs and the generic P will be unused resulting in errors. 196 | if outputs.is_empty() { 197 | return quote! {}; 198 | } 199 | 200 | let method_idents = methods 201 | .iter() 202 | .filter(|IpcMethod { attrs, .. }| !attrs.is_event) 203 | .map(|IpcMethod { ident, .. }| ident); 204 | 205 | quote! { 206 | #[allow(non_camel_case_types)] 207 | #vis enum #output_futures_ident { 208 | #( #outputs ),* 209 | } 210 | 211 | impl std::future::Future for #output_futures_ident

{ 212 | type Output = #outputs_ident; 213 | 214 | fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) 215 | -> std::task::Poll<#outputs_ident> 216 | { 217 | unsafe { 218 | match std::pin::Pin::get_unchecked_mut(self) { 219 | #( 220 | #output_futures_ident::#method_idents(resp) => 221 | std::pin::Pin::new_unchecked(resp) 222 | .poll(cx) 223 | .map(#outputs_ident::#method_idents), 224 | )* 225 | } 226 | } 227 | } 228 | } 229 | 230 | } 231 | } 232 | 233 | fn procedures_handler(&self) -> TokenStream2 { 234 | let &Self { 235 | trait_ident, 236 | handler_ident, 237 | vis, 238 | alias_method_idents, 239 | methods, 240 | ref export_path, 241 | ref path_prefix, 242 | .. 243 | } = self; 244 | 245 | let invoke = format_ident!("__tauri_invoke__"); 246 | let message = format_ident!("__tauri_message__"); 247 | let resolver = format_ident!("__tauri_resolver__"); 248 | 249 | let procedure_handlers = alias_method_idents.iter().zip(methods.iter()).filter_map( 250 | |( 251 | proc_name, 252 | IpcMethod { 253 | ident, args, attrs, .. 254 | }, 255 | )| { 256 | if attrs.is_event { 257 | return None; 258 | } 259 | let args = parse_args(args, &message, ident).unwrap(); 260 | 261 | Some(quote! { stringify!(#proc_name) => { 262 | #resolver.respond_async_serialized(async move { 263 | let res = #trait_ident::#ident( 264 | self.methods, #( #args.unwrap() ),* 265 | ); 266 | let kind = (&res).async_kind(); 267 | kind.future(res).await 268 | }); 269 | }}) 270 | }, 271 | ); 272 | 273 | // Generate json object containing the order and names of the arguments for the methods. 274 | let mut args_map = HashMap::new(); 275 | alias_method_idents 276 | .iter() 277 | .zip(methods) 278 | .for_each(|(ident, IpcMethod { args, .. })| { 279 | let args = args 280 | .iter() 281 | .filter(|arg| !arg.skip_type) 282 | .map(parse_arg_key) 283 | .map(|r| r.unwrap()) 284 | .collect::>(); 285 | 286 | args_map.insert(ident.to_string(), args); 287 | }); 288 | 289 | let serialized_args_map = serde_json::to_string(&args_map).unwrap(); 290 | let export_path = match export_path { 291 | Some(path) => quote! { Some(#path) }, 292 | None => quote! { None }, 293 | }; 294 | 295 | let fn_names = alias_method_idents 296 | .iter() 297 | .map(|ident| fn_ident(trait_ident, ident)); 298 | 299 | quote! { 300 | #[derive(Clone)] 301 | #vis struct #handler_ident

{ 302 | methods: P, 303 | } 304 | 305 | use ::tauri::ipc::private::*; 306 | impl taurpc::TauRpcHandler for #handler_ident

{ 307 | const TRAIT_NAME: &'static str = stringify!(#trait_ident); 308 | const PATH_PREFIX: &'static str = #path_prefix; 309 | const EXPORT_PATH: Option<&'static str> = #export_path; 310 | 311 | fn handle_incoming_request(self, #invoke: tauri::ipc::Invoke) { 312 | #[allow(unused_variables)] 313 | let ::tauri::ipc::Invoke { message: #message, resolver: #resolver, .. } = #invoke; 314 | 315 | // Remove `TauRpc__` prefix 316 | let prefix = #message.command()[8..].to_string(); 317 | let mut prefix = prefix.split(".").collect::>(); 318 | // // Get the actual name of the command 319 | let cmd_name = prefix.pop().unwrap().to_string(); 320 | 321 | match cmd_name.as_str() { 322 | #( #procedure_handlers ),* 323 | _ => { 324 | #resolver.reject(format!("message `{}` not found", #message.command())); 325 | } 326 | }; 327 | } 328 | 329 | fn spawn(self) -> tokio::sync::broadcast::Sender>> { 330 | let (tx, mut rx) = tokio::sync::broadcast::channel(32); 331 | 332 | tokio::spawn(async move { 333 | while let Ok(invoke) = rx.recv().await { 334 | if let Some(invoke) = std::sync::Arc::into_inner(invoke) { 335 | self.clone().handle_incoming_request(invoke); 336 | } 337 | } 338 | }); 339 | 340 | tx 341 | } 342 | 343 | fn args_map() -> String { 344 | #serialized_args_map.to_string() 345 | } 346 | 347 | fn collect_fn_types(mut types_map: &mut specta::TypeCollection) -> Vec { 348 | specta::function::collect_functions![#( #fn_names ),*](&mut types_map) 349 | } 350 | } 351 | } 352 | } 353 | 354 | fn event_trigger_struct(&self) -> TokenStream2 { 355 | let &Self { 356 | vis, 357 | event_trigger_ident, 358 | .. 359 | } = self; 360 | 361 | quote! { 362 | #[derive(Clone, Debug)] 363 | #vis struct #event_trigger_ident(taurpc::EventTrigger); 364 | } 365 | } 366 | 367 | fn impl_event_trigger(&self) -> TokenStream2 { 368 | let &Self { 369 | event_trigger_ident, 370 | vis, 371 | methods, 372 | inputs_ident, 373 | alias_method_idents, 374 | ref path_prefix, 375 | .. 376 | } = self; 377 | 378 | let method_triggers = alias_method_idents 379 | .iter() 380 | .zip(methods) 381 | .filter_map( 382 | |( 383 | alias_ident, 384 | IpcMethod { 385 | ident, 386 | args, 387 | generics, 388 | attrs, 389 | .. 390 | }, 391 | )| { 392 | // skip methods that are not marked as events 393 | if !attrs.is_event { 394 | return None; 395 | } 396 | 397 | let args = args.iter().filter(|arg| !arg.skip_type).collect::>(); 398 | let arg_pats = args.iter().map(|arg| arg.pat()).collect::>(); 399 | 400 | Some(quote! { 401 | #[allow(unused)] 402 | #vis fn #ident #generics(&self, #( #args ),*) -> tauri::Result<()> { 403 | let proc_name = stringify!(#alias_ident); 404 | let req = #inputs_ident::#alias_ident(( #( #arg_pats ),* )); 405 | 406 | self.0.call(proc_name, req) 407 | } 408 | }) 409 | }, 410 | ) 411 | .collect::>(); 412 | 413 | quote! { 414 | impl #event_trigger_ident { 415 | /// Generate a new client to trigger events on the client-side. 416 | #vis fn new(app_handle: tauri::AppHandle) -> Self { 417 | let trigger = taurpc::EventTrigger::new(app_handle, String::from(#path_prefix)); 418 | 419 | Self(trigger) 420 | } 421 | 422 | /// Trigger an event with a specific target. 423 | #vis fn send_to>(&self, target: I) -> Self { 424 | let trigger = taurpc::EventTrigger::new_scoped_from_trigger(self.0.clone(), target.into()); 425 | Self(trigger) 426 | } 427 | 428 | #( #method_triggers )* 429 | } 430 | } 431 | } 432 | } 433 | 434 | impl ToTokens for ProceduresGenerator<'_> { 435 | fn to_tokens(&self, tokens: &mut TokenStream2) { 436 | tokens.extend(vec![ 437 | self.procedures_trait(), 438 | self.procedures_handler(), 439 | self.input_enum(), 440 | self.output_enum(), 441 | self.output_futures(), 442 | self.event_trigger_struct(), 443 | self.impl_event_trigger(), 444 | ]) 445 | } 446 | } 447 | 448 | fn fn_ident(trait_ident: &Ident, fn_ident: &Ident) -> Ident { 449 | format_ident!("{trait_ident}_taurpc_fn__{fn_ident}") 450 | } 451 | -------------------------------------------------------------------------------- /taurpc/taurpc-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use generator::ProceduresGenerator; 2 | use proc::{IpcMethod, Procedures}; 3 | use proc_macro::{self, TokenStream}; 4 | use quote::{format_ident, quote, ToTokens}; 5 | use syn::{ 6 | parse_macro_input, parse_quote, parse_quote_spanned, spanned::Spanned, Ident, ImplItem, 7 | ImplItemFn, ImplItemType, ItemImpl, ItemStruct, ReturnType, Type, 8 | }; 9 | 10 | mod args; 11 | mod attrs; 12 | mod generator; 13 | mod proc; 14 | 15 | use crate::attrs::ProceduresAttrs; 16 | 17 | /// https://github.com/google/tarpc/blob/master/plugins/src/lib.rs#L29 18 | /// Accumulates multiple errors into a result. 19 | /// Only use this for recoverable errors, i.e. non-parse errors. Fatal errors should early exit to 20 | /// avoid further complications. 21 | macro_rules! extend_errors { 22 | ($errors: ident, $e: expr) => { 23 | match $errors { 24 | Ok(_) => $errors = Err($e), 25 | Err(ref mut errors) => errors.extend($e), 26 | } 27 | }; 28 | } 29 | pub(crate) use extend_errors; 30 | 31 | /// Add this macro to all structs used inside the procedures arguments or return types. 32 | /// This macro is necessary for serialization and TS type generation. 33 | #[proc_macro_attribute] 34 | pub fn ipc_type(_attr: TokenStream, item: TokenStream) -> TokenStream { 35 | let input = parse_macro_input!(item as ItemStruct); 36 | 37 | quote! { 38 | #[derive(taurpc::serde::Serialize, taurpc::serde::Deserialize, taurpc::specta_macros::Type, Clone)] 39 | #input 40 | } 41 | .into() 42 | } 43 | 44 | /// Generates the necessary structs and enums for handling calls and generating TS-types. 45 | #[proc_macro_attribute] 46 | pub fn procedures(attrs: TokenStream, item: TokenStream) -> TokenStream { 47 | let procedures_attrs = parse_macro_input!(attrs as ProceduresAttrs); 48 | 49 | let Procedures { 50 | ref ident, 51 | ref methods, 52 | ref vis, 53 | ref generics, 54 | ref attrs, 55 | } = parse_macro_input!(item as Procedures); 56 | 57 | let unit_type: &Type = &parse_quote!(()); 58 | 59 | ProceduresGenerator { 60 | trait_ident: ident, 61 | handler_ident: &format_ident!("TauRpc{}Handler", ident), 62 | event_trigger_ident: &procedures_attrs 63 | .event_trigger_ident 64 | .unwrap_or(format_ident!("TauRpc{}EventTrigger", ident)), 65 | export_path: procedures_attrs.export_to, 66 | path_prefix: procedures_attrs.path, 67 | inputs_ident: &format_ident!("TauRpc{}Inputs", ident), 68 | outputs_ident: &format_ident!("TauRpc{}Outputs", ident), 69 | output_futures_ident: &format_ident!("TauRpc{}OutputFutures", ident), 70 | methods, 71 | method_output_types: &methods 72 | .iter() 73 | .map(|IpcMethod { output, .. }| match output { 74 | ReturnType::Type(_, ref ty) => ty, 75 | ReturnType::Default => unit_type, 76 | }) 77 | .collect::>(), 78 | alias_method_idents: &methods 79 | .iter() 80 | .map(|IpcMethod { ident, attrs, .. }| { 81 | attrs 82 | .alias 83 | .as_ref() 84 | .map(|alias| Ident::new(alias, ident.span())) 85 | .unwrap_or(ident.clone()) 86 | }) 87 | .collect::>(), 88 | vis, 89 | generics, 90 | attrs, 91 | } 92 | .into_token_stream() 93 | .into() 94 | } 95 | 96 | /// Transforms all methods to return `Pin>>`, async traits are not supported. 97 | #[proc_macro_attribute] 98 | pub fn resolvers(_attr: TokenStream, item: TokenStream) -> TokenStream { 99 | let mut item = syn::parse_macro_input!(item as ItemImpl); 100 | let mut types: Vec = Vec::new(); 101 | 102 | for inner in &mut item.items { 103 | if let ImplItem::Fn(method) = inner { 104 | if method.sig.asyncness.is_some() { 105 | types.push(transform_method(method)); 106 | } 107 | } 108 | } 109 | 110 | // add the type declarations into the impl block 111 | for t in types.into_iter() { 112 | item.items.push(syn::ImplItem::Type(t)); 113 | } 114 | 115 | quote!(#item).into() 116 | } 117 | 118 | /// Transform an async method into a sync one that returns a `Pin>`. 119 | fn transform_method(method: &mut ImplItemFn) -> ImplItemType { 120 | method.sig.asyncness = None; 121 | 122 | let ret = match &method.sig.output { 123 | ReturnType::Default => quote!(()), 124 | ReturnType::Type(_, ret) => quote!(#ret), 125 | }; 126 | 127 | let fut_ident = method_fut_ident(&method.sig.ident); 128 | 129 | method.sig.output = parse_quote! { 130 | -> ::core::pin::Pin + ::core::marker::Send 132 | >> 133 | }; 134 | 135 | // transform the body of the method into Box::pin(async move { body }). 136 | let block = method.block.clone(); 137 | method.block = parse_quote_spanned! {method.span()=>{ 138 | Box::pin(async move #block) 139 | }}; 140 | 141 | // generate and return type declaration for return type. 142 | let t = parse_quote! { 143 | type #fut_ident = ::core::pin::Pin + ::core::marker::Send>>; 144 | }; 145 | 146 | t 147 | } 148 | 149 | fn method_fut_ident(ident: &Ident) -> Ident { 150 | format_ident!("{}Fut", ident) 151 | } 152 | -------------------------------------------------------------------------------- /taurpc/taurpc-macros/src/proc.rs: -------------------------------------------------------------------------------- 1 | use super::extend_errors; 2 | use syn::{ 3 | braced, 4 | ext::IdentExt, 5 | parenthesized, 6 | parse::{self, Parse, ParseStream}, 7 | spanned::Spanned, 8 | Attribute, FnArg, Generics, Ident, Pat, ReturnType, Token, Visibility, 9 | }; 10 | 11 | use crate::{args::Arg, attrs::MethodAttrs}; 12 | 13 | /// Parse the structure of the procedures trait tagged with `#[taurpc::procedures]`. 14 | pub struct Procedures { 15 | pub ident: Ident, 16 | pub methods: Vec, 17 | pub vis: Visibility, 18 | pub generics: Generics, 19 | pub attrs: Vec, 20 | } 21 | 22 | /// Parse the structure of the methods insdie the procedures trait tagged with `#[taurpc::procedures]`. 23 | /// These methods can have generics and also have attributes e.g.: `#[taurpc(skip, alias = "...")]`. 24 | pub struct IpcMethod { 25 | pub ident: Ident, 26 | pub output: ReturnType, 27 | pub args: Vec, 28 | pub generics: Generics, 29 | pub attrs: MethodAttrs, 30 | } 31 | 32 | impl Parse for Procedures { 33 | fn parse(input: ParseStream) -> parse::Result { 34 | let attrs = input.call(Attribute::parse_outer)?; 35 | let vis = input.parse()?; 36 | ::parse(input)?; 37 | let ident: Ident = input.parse()?; 38 | 39 | let generics: Generics = input.parse()?; 40 | 41 | let content; 42 | braced!(content in input); 43 | 44 | let mut methods = Vec::new(); 45 | while !content.is_empty() { 46 | let method = ::parse(&content)?; 47 | if method.attrs.skip { 48 | continue; 49 | } 50 | methods.push(method); 51 | } 52 | 53 | let mut ident_errors = Ok(()); 54 | for procedure in &methods { 55 | if procedure.ident == "into_handler" { 56 | extend_errors!( 57 | ident_errors, 58 | syn::Error::new( 59 | procedure.ident.span(), 60 | format!( 61 | "method name conflicts with generated fn `{}::into_handler`", 62 | ident.unraw() 63 | ), 64 | ) 65 | ); 66 | } 67 | 68 | if procedure.ident == "setup" { 69 | extend_errors!( 70 | ident_errors, 71 | syn::Error::new( 72 | procedure.ident.span(), 73 | format!( 74 | "method name conflicts with generated fn `{}::setup`", 75 | ident.unraw() 76 | ), 77 | ) 78 | ); 79 | } 80 | 81 | if procedure.ident == "send_to" { 82 | extend_errors!( 83 | ident_errors, 84 | syn::Error::new( 85 | procedure.ident.span(), 86 | format!( 87 | "method name conflicts with generated fn `{}::send_to, this method is used to send scoped events`", 88 | ident.unraw() 89 | ), 90 | ) 91 | ); 92 | } 93 | } 94 | ident_errors?; 95 | 96 | Ok(Procedures { 97 | ident, 98 | methods, 99 | vis, 100 | generics, 101 | attrs, 102 | }) 103 | } 104 | } 105 | 106 | impl Parse for IpcMethod { 107 | fn parse(input: ParseStream) -> parse::Result { 108 | let attrs = MethodAttrs::parse(input)?; 109 | 110 | ::parse(input)?; 111 | ::parse(input)?; 112 | 113 | let ident: Ident = input.parse()?; 114 | let generics: Generics = input.parse()?; 115 | 116 | let content; 117 | parenthesized!(content in input); 118 | 119 | let mut args = Vec::new(); 120 | for arg in content.parse_terminated(FnArg::parse, Token![,])? { 121 | match arg { 122 | FnArg::Typed(pat_ty) if matches!(*pat_ty.pat, Pat::Ident(_)) => { 123 | args.push(Arg::from(pat_ty)); 124 | } 125 | err => { 126 | return Err(syn::Error::new( 127 | err.span(), 128 | "only named arguments are allowed", 129 | )) 130 | } 131 | } 132 | } 133 | 134 | let output = input.parse()?; 135 | ::parse(input)?; 136 | 137 | Ok(IpcMethod { 138 | ident, 139 | output, 140 | args, 141 | generics, 142 | attrs, 143 | }) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "allowSyntheticDefaultImports": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noUncheckedIndexedAccess": true, 12 | "noEmit": true, 13 | "isolatedModules": true 14 | }, 15 | "include": [ 16 | "src", 17 | ".eslintrc.cjs" 18 | ], 19 | "exclude": [ 20 | "node_modules", 21 | "dist" 22 | ] 23 | } 24 | --------------------------------------------------------------------------------