diff --git a/sdks/typescript/.changeset/README.md b/sdks/typescript/.changeset/README.md new file mode 100644 index 00000000000..e5b6d8d6a67 --- /dev/null +++ b/sdks/typescript/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/sdks/typescript/.changeset/config.json b/sdks/typescript/.changeset/config.json new file mode 100644 index 00000000000..362ba234a2e --- /dev/null +++ b/sdks/typescript/.changeset/config.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json", + "changelog": [ + "@changesets/changelog-github", + { "repo": "clockworklabs/spacetimedb-typescript-sdk" } + ], + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/sdks/typescript/.gitattributes b/sdks/typescript/.gitattributes new file mode 100644 index 00000000000..0046e615f49 --- /dev/null +++ b/sdks/typescript/.gitattributes @@ -0,0 +1,2 @@ +src/client_api/*.ts linguist-generated=true +examples/quickstart/client/src/module_bindings/*.ts linguist-generated=true diff --git a/sdks/typescript/.github/pull_request_template.md b/sdks/typescript/.github/pull_request_template.md new file mode 100644 index 00000000000..4b7720bd2d4 --- /dev/null +++ b/sdks/typescript/.github/pull_request_template.md @@ -0,0 +1,19 @@ +## Description of Changes + +_Describe what has been changed, any new features or bug fixes_ + +## API + +- [ ] This is an API breaking change to the SDK + +_If the API is breaking, please state below what will break_ + +## Requires SpacetimeDB PRs + +_List any PRs here that are required for this SDK change to work_ + +## Testing + +_Write instructions for a test that you performed for this PR_ + +- [ ] Describe a test for this PR that you have completed diff --git a/sdks/typescript/.github/spacetimedb-version.txt b/sdks/typescript/.github/spacetimedb-version.txt new file mode 100644 index 00000000000..1f7391f92b6 --- /dev/null +++ b/sdks/typescript/.github/spacetimedb-version.txt @@ -0,0 +1 @@ +master diff --git a/sdks/typescript/.github/workflows/cr.yml b/sdks/typescript/.github/workflows/cr.yml new file mode 100644 index 00000000000..01353168c40 --- /dev/null +++ b/sdks/typescript/.github/workflows/cr.yml @@ -0,0 +1,24 @@ +name: Continuous Releases + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + + - uses: pnpm/action-setup@v4 + with: + version: 9.7 + run_install: true + + - name: Build + run: pnpm compile + + - name: Release + run: cd packages/sdk && pnpm dlx pkg-pr-new publish --compact --pnpm diff --git a/sdks/typescript/.github/workflows/lint.yml b/sdks/typescript/.github/workflows/lint.yml new file mode 100644 index 00000000000..8adf4aa22aa --- /dev/null +++ b/sdks/typescript/.github/workflows/lint.yml @@ -0,0 +1,41 @@ +name: Lint + +on: + pull_request: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + + - uses: pnpm/action-setup@v4 + with: + version: 9.7 + run_install: true + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Lint + run: pnpm lint diff --git a/sdks/typescript/.github/workflows/release.yml b/sdks/typescript/.github/workflows/release.yml new file mode 100644 index 00000000000..5f97085ad9f --- /dev/null +++ b/sdks/typescript/.github/workflows/release.yml @@ -0,0 +1,40 @@ +name: Publish +on: + push: + branches: + - 'main' + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: write + packages: write + pull-requests: write + issues: read + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: 9.7 + run_install: true + + - uses: actions/setup-node@v4 + with: + node-version: 18.x + cache: 'pnpm' + + - run: pnpm compile + + - name: Create Release Pull Request or Publish + id: changesets + uses: changesets/action@v1 + with: + publish: pnpm run ci:release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/sdks/typescript/.github/workflows/test.yml b/sdks/typescript/.github/workflows/test.yml new file mode 100644 index 00000000000..4a55076fcb0 --- /dev/null +++ b/sdks/typescript/.github/workflows/test.yml @@ -0,0 +1,146 @@ +name: Tests + +on: + push: + branches: + - main + - master + pull_request: + +jobs: + compile-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + + - uses: pnpm/action-setup@v4 + with: + version: 9.7 + run_install: true + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Compile + run: pnpm compile + + - name: Run sdk tests + working-directory: packages/sdk + run: pnpm test + + # - name: Extract SpacetimeDB branch name from file + # id: extract-branch + # run: | + # # Define the path to the branch file + # BRANCH_FILE=".github/spacetimedb-branch.txt" + + # # Default to master if file doesn't exist + # if [ ! -f "$BRANCH_FILE" ]; then + # echo "::notice::No SpacetimeDB branch file found, using 'master'" + # echo "branch=master" >> $GITHUB_OUTPUT + # exit 0 + # fi + + # # Read and trim whitespace from the file + # branch=$(cat "$BRANCH_FILE" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') + + # # Fallback to master if empty + # if [ -z "$branch" ]; then + # echo "::warning::SpacetimeDB branch file is empty, using 'master'" + # branch="master" + # fi + + # echo "branch=$branch" >> $GITHUB_OUTPUT + # echo "Using SpacetimeDB branch from file: $branch" + + - name: Checkout SpacetimeDB + uses: actions/checkout@v4 + with: + repository: clockworklabs/SpacetimeDB + # ref: ${{ steps.extract-branch.outputs.branch }} + path: SpacetimeDB + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: SpacetimeDB/modules/quickstart-chat + shared-key: quickstart-chat-test + + - name: Install SpacetimeDB CLI from the local checkout + run: | + cargo install --force --path SpacetimeDB/crates/cli --locked --message-format=short + cargo install --force --path SpacetimeDB/crates/standalone --locked --message-format=short + # Add a handy alias using the old binary name, so that we don't have to rewrite all scripts (incl. in submodules). + rm -f $HOME/.cargo/bin/spacetime + ln -s $HOME/.cargo/bin/spacetimedb-cli $HOME/.cargo/bin/spacetime + # Clear any existing information + spacetime server clear -y + env: + # Share the target directory with our local project to avoid rebuilding same SpacetimeDB crates twice. + CARGO_TARGET_DIR: SpacetimeDB/modules/quickstart-chat/target + + - name: Generate client bindings + working-directory: SpacetimeDB/modules/quickstart-chat + run: | + spacetime generate --lang typescript --out-dir ../../../examples/quickstart-chat/src/module_bindings + pnpm lint --write + + - name: Check for changes + run: | + # This was copied from SpacetimeDB/tools/check-diff.sh. + # It's required because `spacetime generate` creates lines with the SpacetimeDB commit + # version, which would make this `git diff` check very brittle if included. + PATTERN='^// This was generated using spacetimedb cli version.*' + if ! git diff --exit-code --ignore-matching-lines="$PATTERN" -- examples/quickstart-chat/src/module_bindings; then + echo "Error: Bindings are dirty. Please generate bindings again and commit them to this branch." + exit 1 + fi + + # - name: Start SpacetimeDB + # run: | + # spacetime start & + # disown + + # - name: Publish module to SpacetimeDB + # working-directory: SpacetimeDB/modules/quickstart-chat + # run: | + # spacetime logout && spacetime login --server-issued-login local + # spacetime publish -s local quickstart-chat -c -y + + # - name: Publish module to SpacetimeDB + # working-directory: SpacetimeDB/modules/quickstart-chat + # run: | + # spacetime logs quickstart-chat + + - name: Check that quickstart-chat builds + working-directory: examples/quickstart-chat + run: pnpm build + + # - name: Run quickstart-chat tests + # working-directory: examples/quickstart-chat + # run: pnpm test + # + # # Run this step always, even if the previous steps fail + # - name: Print rows in the user table + # if: always() + # run: spacetime sql quickstart-chat "SELECT * FROM user" diff --git a/sdks/typescript/.gitignore b/sdks/typescript/.gitignore new file mode 100644 index 00000000000..87e920e5947 --- /dev/null +++ b/sdks/typescript/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +.envrc +dist/ +.DS_Store \ No newline at end of file diff --git a/sdks/typescript/.npmignore b/sdks/typescript/.npmignore new file mode 100644 index 00000000000..f7f8970df46 --- /dev/null +++ b/sdks/typescript/.npmignore @@ -0,0 +1,3 @@ +tests +.envrc +node_modules diff --git a/sdks/typescript/.prettierignore b/sdks/typescript/.prettierignore new file mode 100644 index 00000000000..3592bad75bc --- /dev/null +++ b/sdks/typescript/.prettierignore @@ -0,0 +1,5 @@ +node_modules +pnpm-lock.yaml +dist +target +/.github diff --git a/sdks/typescript/.prettierrc b/sdks/typescript/.prettierrc new file mode 100644 index 00000000000..2921455b325 --- /dev/null +++ b/sdks/typescript/.prettierrc @@ -0,0 +1,11 @@ +{ + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "arrowParens": "avoid", + "jsxSingleQuote": false, + "trailingComma": "es5", + "endOfLine": "auto", + "printWidth": 80 +} diff --git a/sdks/typescript/.vscode/settings.json b/sdks/typescript/.vscode/settings.json new file mode 100644 index 00000000000..d9546cfe0b8 --- /dev/null +++ b/sdks/typescript/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "workbench.colorCustomizations": { + "[Material Theme Darker]": {}, + "minimap.background": "#00000000", + "scrollbar.shadow": "#00000000" + } +} diff --git a/sdks/typescript/DEVELOP.md b/sdks/typescript/DEVELOP.md new file mode 100644 index 00000000000..f01377803f1 --- /dev/null +++ b/sdks/typescript/DEVELOP.md @@ -0,0 +1,43 @@ +# Notes for maintainers + +The directory `packages/sdk/src/client_api` is generated from [the SpacetimeDB client-api-messages](https://github.com/clockworklabs/SpacetimeDB/tree/master/crates/client-api-messages). +This is not automated. +Whenever the `client-api-messages` crate changes, you'll have to manually re-generate the definitions. +See that crate's DEVELOP.md for how to do this. + +The generated files must be manually modified to fix their imports from the rest of the SDK. + +Within each generated file: + +- Change the import from `"@clockworklabs/spacetimedb-sdk"` to `"../index"`. + +On a mac, you can do that by running this in the directory: `find . -type f -exec sed -i '' 's/"@clockworklabs\/spacetimedb-sdk"/"..\/index"/g' {} \;`. + +## Releases and publishing + +Every Pull Request with a public-facing change (Bug fix, perf, feature etc) must be accompanied by a changeset. Any person working on a patch or feature needs to run `pnpm -w changeset` command, which will prompt them to select packages changed. Choose `@clockworklabs/spacetimedb-sdk` + +![image](https://github.com/user-attachments/assets/3a69ff1f-c92b-459a-8dcc-d8fea53f77b4) + +Next it will ask whether you'd like to add a Major tag to it. Hit enter to go to minor tag. If its a minor change(In our case, minor is major until v1 comes out, as in every minor can have breaking changes). If its a patch change(Or minor for prerelease time), then again hit enter + +After selecting the correct tag, it will ask you for a message + +![image](https://github.com/user-attachments/assets/d05a338b-965d-4669-8155-542d0225b257) +![image](https://github.com/user-attachments/assets/7abc830e-4590-42e7-bce8-86155d86c672) +![image](https://github.com/user-attachments/assets/8f3b16bd-b01d-4117-8d02-3887f1d308dd) + +Once that is done, hit enter. It will generate a `.md` file which you can then push to github. This all has to be done in the PR with the feature/fix in it. + +We can merge it instantly to do a release, or we can merge PRs with their own Changesets. E.g. Any new feature or patch we work on for 1.0 now, should have a Changeset in it. All of these will accumulate in the "Version Packages" PR. Once all these are satisfactorily done, we merge this PR, which will + +- Release the package on npm +- Release on Github tags +- Update CHANGELOG.md + +**NOTE: It is very important that no one manually runs `npm publish`. We have provenance enabled on this package, means each version will be signed by github and traceable to the very commit associated to it** + +Publishing manually will breach the provenance contract, and alert security servcies like Snyk into investigating the package or issuing a warning. npm install of our package will also warn them of potential compromise to the package + +![image](https://github.com/user-attachments/assets/b56282b7-9055-48a0-8a49-3df9d75d481f) +![image](https://github.com/user-attachments/assets/99d023cf-31cc-48a0-93ed-a88c326425c5) diff --git a/sdks/typescript/LICENSE b/sdks/typescript/LICENSE new file mode 100644 index 00000000000..982d9c5e676 --- /dev/null +++ b/sdks/typescript/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 Clockwork Labs, Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/sdks/typescript/README.md b/sdks/typescript/README.md new file mode 100644 index 00000000000..23a253890e0 --- /dev/null +++ b/sdks/typescript/README.md @@ -0,0 +1,3 @@ +`@clockworklabs/spacetimedb-sdk` is a TypeScript SDK for SpacetimeDB. + +Source code can be found here on [GitHub](https://github.com/clockworklabs/spacetimedb-typescript-sdk/blob/main/packages/sdk). diff --git a/sdks/typescript/examples/quickstart-chat/.gitignore b/sdks/typescript/examples/quickstart-chat/.gitignore new file mode 100644 index 00000000000..a547bf36d8d --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/sdks/typescript/examples/quickstart-chat/CHANGELOG.md b/sdks/typescript/examples/quickstart-chat/CHANGELOG.md new file mode 100644 index 00000000000..ecc61b68404 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/CHANGELOG.md @@ -0,0 +1,29 @@ +# quickstart-chat + +## 0.0.1 + +### Patch Changes + +- Updated dependencies [[`cf7b7d8`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/cf7b7d89a1547fb3863f6641f5b2eb40a27c05d8), [`941cf4e`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/941cf4eba6b7df934d74696b373b89cc62764673), [`a501f5c`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/a501f5ccf9a0a926eb4f345ddeb01ffcb872d67e), [`9032269`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/9032269004d4dae587c39ccd85da0a32fb9a0114), [`6547882`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/6547882bb28ed9a1ca436335745e9997328026ff), [`5d7304b`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/5d7304bd3e05dd7a032cfb7069aab97b881f0179)]: + - @clockworklabs/spacetimedb-sdk@1.2.0 + +## 0.0.3-rc1.0 + +### Patch Changes + +- Updated dependencies [[`cf7b7d8`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/cf7b7d89a1547fb3863f6641f5b2eb40a27c05d8), [`a501f5c`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/a501f5ccf9a0a926eb4f345ddeb01ffcb872d67e), [`9032269`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/9032269004d4dae587c39ccd85da0a32fb9a0114), [`6547882`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/6547882bb28ed9a1ca436335745e9997328026ff), [`5d7304b`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/5d7304bd3e05dd7a032cfb7069aab97b881f0179)]: + - @clockworklabs/spacetimedb-sdk@1.0.0-rc1.0 + +## 0.0.2 + +### Patch Changes + +- Updated dependencies [[`2f6c82c`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/2f6c82c724b9f9407c7bedee13252ca8ffab8f7d), [`b9db9b6`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/b9db9b6e46d8c98b29327d97c12c07b7a2fc96bf), [`79c278b`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/79c278be71b2dfd82106ada983fd81d395b1d912)]: + - @clockworklabs/spacetimedb-sdk@0.12.1 + +## 0.0.1 + +### Patch Changes + +- Updated dependencies [[`5adb557`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/5adb55776c81d0760cf0268df0fa5dee600f0ef8), [`ab1f463`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/ab1f463d7da6e530a6cd47e2433141bfd16addd1), [`b8c944c`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/b8c944cd23d3b53c72131803a775127bf0a95213), [`17227c0`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/17227c0f65def3a9d5e767756ccf46777210041a)]: + - @clockworklabs/spacetimedb-sdk@0.12.0 diff --git a/sdks/typescript/examples/quickstart-chat/README.md b/sdks/typescript/examples/quickstart-chat/README.md new file mode 100644 index 00000000000..195abec4590 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/README.md @@ -0,0 +1,67 @@ +# SpacetimeDB TypeScript Quickstart Chat + +This is a simple chat application that demonstrates how to use SpacetimeDB with TypeScript and React. The chat application is a simple chat room where users can send messages to each other. The chat application uses SpacetimeDB to store the chat messages. + +It is based directly on the plain React + TypeScript + Vite template. You can follow the quickstart guide for how creating this project from scratch at [SpacetimeDB TypeScript Quickstart](https://spacetimedb.com/docs/sdks/typescript/quickstart). + +You can follow the instructions for creating your own SpacetimeDB module here: [SpacetimeDB Rust Module Quickstart](https://spacetimedb.com/docs/modules/rust/quickstart). Place the module in the `quickstart-chat/server` directory for compability with this project. + +In order to run this example, you need to: + +- `pnpm compile` in the root directory (`spacetimedb-typescriptsdk`) +- `pnpm install` in this directory +- `pnpm run build` in this directory +- `pnpm run dev` in this directory to run the example + +Below is copied from the original template README: + +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default tseslint.config({ + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}); +``` + +- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` +- Optionally add `...tseslint.configs.stylisticTypeChecked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: + +```js +// eslint.config.js +import react from 'eslint-plugin-react'; + +export default tseslint.config({ + // Set the react version + settings: { react: { version: '18.3' } }, + plugins: { + // Add the react plugin + react, + }, + rules: { + // other rules... + // Enable its recommended rules + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + }, +}); +``` diff --git a/sdks/typescript/examples/quickstart-chat/eslint.config.js b/sdks/typescript/examples/quickstart-chat/eslint.config.js new file mode 100644 index 00000000000..82c2e20ccc2 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js'; +import globals from 'globals'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + } +); diff --git a/sdks/typescript/examples/quickstart-chat/index.html b/sdks/typescript/examples/quickstart-chat/index.html new file mode 100644 index 00000000000..e4b78eae123 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/sdks/typescript/examples/quickstart-chat/package.json b/sdks/typescript/examples/quickstart-chat/package.json new file mode 100644 index 00000000000..bf138510c87 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/package.json @@ -0,0 +1,39 @@ +{ + "name": "client", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview", + "test": "vitest", + "spacetime:generate-bindings": "spacetime generate --lang typescript --out-dir src/module_bindings --project-path server", + "spacetime:publish:local": "spacetime publish chat --project-path server --server local", + "spacetime:publish": "spacetime publish chat --project-path server --server testnet" + }, + "dependencies": { + "@clockworklabs/spacetimedb-sdk": "workspace:*", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@eslint/js": "^9.17.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.2.0", + "@testing-library/user-event": "^14.6.1", + "@types/jest": "^29.5.14", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.17.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.16", + "globals": "^15.14.0", + "jsdom": "^26.0.0", + "typescript": "~5.6.2", + "typescript-eslint": "^8.18.2", + "vite": "^6.0.5" + } +} diff --git a/sdks/typescript/examples/quickstart-chat/public/vite.svg b/sdks/typescript/examples/quickstart-chat/public/vite.svg new file mode 100644 index 00000000000..e7b8dfb1b2a --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sdks/typescript/examples/quickstart-chat/src/.gitattributes b/sdks/typescript/examples/quickstart-chat/src/.gitattributes new file mode 100644 index 00000000000..f4d6534ab2c --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/.gitattributes @@ -0,0 +1 @@ +/module_bindings/** linguist-generated=true diff --git a/sdks/typescript/examples/quickstart-chat/src/App.css b/sdks/typescript/examples/quickstart-chat/src/App.css new file mode 100644 index 00000000000..aea01ce5358 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/App.css @@ -0,0 +1,116 @@ +.App { + display: grid; + /* + 3 rows: + 1) Profile + 2) Main content (left = message, right = system) + 3) New message + */ + grid-template-rows: auto 1fr auto; + /* 2 columns: left for chat, right for system */ + grid-template-columns: 2fr 1fr; + + height: 100vh; /* fill viewport height */ + width: clamp(300px, 100%, 1200px); + margin: 0 auto; +} + +/* ----- Profile (Row 1, spans both columns) ----- */ +.profile { + grid-column: 1 / 3; + display: flex; + align-items: center; + gap: 1rem; + padding: 1rem; + border-bottom: 1px solid var(--theme-color); +} + +.profile h1 { + margin-right: auto; /* pushes name/edit form to the right */ +} + +.profile form { + display: flex; + flex-grow: 1; + align-items: center; + gap: 0.5rem; + max-width: 300px; +} + +.profile form input { + background-color: var(--textbox-color); +} + +/* ----- Chat Messages (Row 2, Col 1) ----- */ +.message { + grid-row: 2 / 3; + grid-column: 1 / 2; + + /* Ensure this section scrolls if content is long */ + overflow-y: auto; + padding: 1rem; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.message h1 { + margin-right: 0.5rem; +} + +/* ----- System Panel (Row 2, Col 2) ----- */ +.system { + grid-row: 2 / 3; + grid-column: 2 / 3; + + /* Also scroll independently if needed */ + overflow-y: auto; + padding: 1rem; + border-left: 1px solid var(--theme-color); + white-space: pre-wrap; + font-family: monospace; +} + +/* ----- New Message (Row 3, spans columns 1-2) ----- */ +.new-message { + grid-column: 1 / 3; + display: flex; + justify-content: center; + align-items: center; + padding: 1rem; + border-top: 1px solid var(--theme-color); +} + +.new-message form { + display: flex; + flex-direction: column; + gap: 0.75rem; + width: 100%; + max-width: 600px; +} + +.new-message form h3 { + margin-bottom: 0.25rem; +} + +/* Distinct background for the textarea */ +.new-message form textarea { + font-family: monospace; + font-weight: 400; + font-size: 1rem; + resize: vertical; + min-height: 80px; + background-color: var(--textbox-color); + color: inherit; + + /* Subtle shadow for visibility */ + box-shadow: + 0 1px 3px rgba(0, 0, 0, 0.12), + 0 1px 2px rgba(0, 0, 0, 0.24); +} + +@media (prefers-color-scheme: dark) { + .new-message form textarea { + box-shadow: 0 0 0 1px #17492b; + } +} diff --git a/sdks/typescript/examples/quickstart-chat/src/App.integration.test.tsx b/sdks/typescript/examples/quickstart-chat/src/App.integration.test.tsx new file mode 100644 index 00000000000..7b567204fe3 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/App.integration.test.tsx @@ -0,0 +1,62 @@ +// src/App.integration.test.tsx +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import App from './App'; + +describe('App Integration Test', () => { + it('connects to the DB, allows name change and message sending', async () => { + render(); + + // Initially, we should see "Connecting..." + expect(screen.getByText(/Connecting.../i)).toBeInTheDocument(); + + // Wait until "Connecting..." is gone (meaning we've connected) + // This might require the actual DB to accept the connection + await waitFor( + () => + expect(screen.queryByText(/Connecting.../i)).not.toBeInTheDocument(), + { timeout: 10000 } + ); + + // The profile section should show the default name or truncated identity + // For example, you can check if the text is rendered. + // If your default identity is something like 'abcdef12' or 'Unknown' + // we do a generic check: + expect( + screen.getByRole('heading', { name: /profile/i }) + ).toBeInTheDocument(); + + // Let's change the user's name + const editNameButton = screen.getByText(/Edit Name/i); + await userEvent.click(editNameButton); + + const nameInput = screen.getByRole('textbox', { name: /name input/i }); + await userEvent.clear(nameInput); + await userEvent.type(nameInput, 'TestUser'); + const submitNameButton = screen.getByRole('button', { name: /submit/i }); + await userEvent.click(submitNameButton); + + // If your DB or UI updates instantly, we can check that the new name shows up + await waitFor( + () => { + expect(screen.getByText('TestUser')).toBeInTheDocument(); + }, + { timeout: 10000 } + ); + + // Now let's send a message + const textarea = screen.getByRole('textbox', { name: /message input/i }); + await userEvent.type(textarea, 'Hello from GH Actions!'); + + const sendButton = screen.getByRole('button', { name: /send/i }); + await userEvent.click(sendButton); + + // Wait for message to appear in the UI + await waitFor( + () => { + expect(screen.getByText('Hello from GH Actions!')).toBeInTheDocument(); + }, + { timeout: 10000 } + ); + }); +}); diff --git a/sdks/typescript/examples/quickstart-chat/src/App.tsx b/sdks/typescript/examples/quickstart-chat/src/App.tsx new file mode 100644 index 00000000000..fa5998cd1eb --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/App.tsx @@ -0,0 +1,270 @@ +import React, { useEffect, useState } from 'react'; +import './App.css'; +import { + DbConnection, + ErrorContext, + EventContext, + Message, + User, +} from './module_bindings'; +import { Identity } from '@clockworklabs/spacetimedb-sdk'; + +export type PrettyMessage = { + senderName: string; + text: string; +}; + +function useMessages(conn: DbConnection | null): Message[] { + const [messages, setMessages] = useState([]); + + useEffect(() => { + if (!conn) return; + const onInsert = (_ctx: EventContext, message: Message) => { + setMessages(prev => [...prev, message]); + }; + conn.db.message.onInsert(onInsert); + + const onDelete = (_ctx: EventContext, message: Message) => { + setMessages(prev => + prev.filter( + m => + m.text !== message.text && + m.sent !== message.sent && + m.sender !== message.sender + ) + ); + }; + conn.db.message.onDelete(onDelete); + + return () => { + conn.db.message.removeOnInsert(onInsert); + conn.db.message.removeOnDelete(onDelete); + }; + }, [conn]); + + return messages; +} + +function useUsers(conn: DbConnection | null): Map { + const [users, setUsers] = useState>(new Map()); + + useEffect(() => { + if (!conn) return; + const onInsert = (_ctx: EventContext, user: User) => { + setUsers(prev => new Map(prev.set(user.identity.toHexString(), user))); + }; + conn.db.user.onInsert(onInsert); + + const onUpdate = (_ctx: EventContext, oldUser: User, newUser: User) => { + setUsers(prev => { + prev.delete(oldUser.identity.toHexString()); + return new Map(prev.set(newUser.identity.toHexString(), newUser)); + }); + }; + conn.db.user.onUpdate(onUpdate); + + const onDelete = (_ctx: EventContext, user: User) => { + setUsers(prev => { + prev.delete(user.identity.toHexString()); + return new Map(prev); + }); + }; + conn.db.user.onDelete(onDelete); + + return () => { + conn.db.user.removeOnInsert(onInsert); + conn.db.user.removeOnUpdate(onUpdate); + conn.db.user.removeOnDelete(onDelete); + }; + }, [conn]); + + return users; +} + +function App() { + const [newName, setNewName] = useState(''); + const [settingName, setSettingName] = useState(false); + const [systemMessage, setSystemMessage] = useState(''); + const [newMessage, setNewMessage] = useState(''); + const [connected, setConnected] = useState(false); + const [identity, setIdentity] = useState(null); + const [conn, setConn] = useState(null); + + useEffect(() => { + const subscribeToQueries = (conn: DbConnection, queries: string[]) => { + conn + ?.subscriptionBuilder() + .onApplied(() => { + console.log('SDK client cache initialized.'); + }) + .subscribe(queries); + }; + + const onConnect = ( + conn: DbConnection, + identity: Identity, + token: string + ) => { + setIdentity(identity); + setConnected(true); + localStorage.setItem('auth_token', token); + console.log( + 'Connected to SpacetimeDB with identity:', + identity.toHexString() + ); + conn.reducers.onSendMessage(() => { + console.log('Message sent.'); + }); + + subscribeToQueries(conn, ['SELECT * FROM message', 'SELECT * FROM user']); + }; + + const onDisconnect = () => { + console.log('Disconnected from SpacetimeDB'); + setConnected(false); + }; + + const onConnectError = (_ctx: ErrorContext, err: Error) => { + console.log('Error connecting to SpacetimeDB:', err); + }; + + setConn( + DbConnection.builder() + .withUri('ws://localhost:3000') + .withModuleName('quickstart-chat') + .withToken(localStorage.getItem('auth_token') || '') + .onConnect(onConnect) + .onDisconnect(onDisconnect) + .onConnectError(onConnectError) + .build() + ); + }, []); + + useEffect(() => { + if (!conn) return; + conn.db.user.onInsert((_ctx, user) => { + if (user.online) { + const name = user.name || user.identity.toHexString().substring(0, 8); + setSystemMessage(prev => prev + `\n${name} has connected.`); + } + }); + conn.db.user.onUpdate((_ctx, oldUser, newUser) => { + const name = + newUser.name || newUser.identity.toHexString().substring(0, 8); + if (oldUser.online === false && newUser.online === true) { + setSystemMessage(prev => prev + `\n${name} has connected.`); + } else if (oldUser.online === true && newUser.online === false) { + setSystemMessage(prev => prev + `\n${name} has disconnected.`); + } + }); + }, [conn]); + + const messages = useMessages(conn); + const users = useUsers(conn); + + const prettyMessages: PrettyMessage[] = messages + .sort((a, b) => (a.sent > b.sent ? 1 : -1)) + .map(message => ({ + senderName: + users.get(message.sender.toHexString())?.name || + message.sender.toHexString().substring(0, 8), + text: message.text, + })); + + if (!conn || !connected || !identity) { + return ( +
+

Connecting...

+
+ ); + } + + const name = + users.get(identity?.toHexString())?.name || + identity?.toHexString().substring(0, 8) || + ''; + + const onSubmitNewName = (e: React.FormEvent) => { + e.preventDefault(); + setSettingName(false); + conn.reducers.setName(newName); + }; + + const onMessageSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setNewMessage(''); + conn.reducers.sendMessage(newMessage); + }; + + return ( +
+
+

Profile

+ {!settingName ? ( + <> +

{name}

+ + + ) : ( +
+ setNewName(e.target.value)} + /> + +
+ )} +
+
+

Messages

+ {prettyMessages.length < 1 &&

No messages

} +
+ {prettyMessages.map((message, key) => ( +
+

+ {message.senderName} +

+

{message.text}

+
+ ))} +
+
+
+

System

+
+

{systemMessage}

+
+
+
+
+

New Message

+ + +
+
+
+ ); +} + +export default App; diff --git a/sdks/typescript/examples/quickstart-chat/src/assets/react.svg b/sdks/typescript/examples/quickstart-chat/src/assets/react.svg new file mode 100644 index 00000000000..6c87de9bb33 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sdks/typescript/examples/quickstart-chat/src/index.css b/sdks/typescript/examples/quickstart-chat/src/index.css new file mode 100644 index 00000000000..9390800cc7e --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/index.css @@ -0,0 +1,76 @@ +/* ----- CSS Reset & Global Settings ----- */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +/* ----- Color Variables ----- */ +:root { + --theme-color: #3dc373; + --theme-color-contrast: #08180e; + --textbox-color: #edfef4; + color-scheme: light dark; +} + +@media (prefers-color-scheme: dark) { + :root { + --theme-color: #4cf490; + --theme-color-contrast: #132219; + --textbox-color: #0f311d; + } +} + +/* ----- Page Setup ----- */ +html, +body, +#root { + height: 100%; + margin: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} + +/* ----- Buttons ----- */ +button { + padding: 0.5rem 0.75rem; + border: none; + border-radius: 0.375rem; + background-color: var(--theme-color); + color: var(--theme-color-contrast); + cursor: pointer; + font-weight: 600; + letter-spacing: 0.1px; + font-family: monospace; +} + +/* ----- Inputs & Textareas ----- */ +input, +textarea { + border: none; + border-radius: 0.375rem; + caret-color: var(--theme-color); + font-family: monospace; + font-weight: 600; + letter-spacing: 0.1px; + padding: 0.5rem 0.75rem; +} + +input:focus, +textarea:focus { + outline: none; + box-shadow: 0 0 0 2px var(--theme-color); +} diff --git a/sdks/typescript/examples/quickstart-chat/src/main.tsx b/sdks/typescript/examples/quickstart-chat/src/main.tsx new file mode 100644 index 00000000000..df655eaec4e --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import './index.css'; +import App from './App.tsx'; + +createRoot(document.getElementById('root')!).render( + + + +); diff --git a/sdks/typescript/examples/quickstart-chat/src/module_bindings/identity_connected_reducer.ts b/sdks/typescript/examples/quickstart-chat/src/module_bindings/identity_connected_reducer.ts new file mode 100644 index 00000000000..fedf44c4e10 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/module_bindings/identity_connected_reducer.ts @@ -0,0 +1,60 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; + +export type IdentityConnected = {}; + +/** + * A namespace for generated helper functions. + */ +export namespace IdentityConnected { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([]); + } + + export function serialize( + writer: BinaryWriter, + value: IdentityConnected + ): void { + IdentityConnected.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): IdentityConnected { + return IdentityConnected.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/examples/quickstart-chat/src/module_bindings/identity_disconnected_reducer.ts b/sdks/typescript/examples/quickstart-chat/src/module_bindings/identity_disconnected_reducer.ts new file mode 100644 index 00000000000..b958d1f82d5 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/module_bindings/identity_disconnected_reducer.ts @@ -0,0 +1,62 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; + +export type IdentityDisconnected = {}; + +/** + * A namespace for generated helper functions. + */ +export namespace IdentityDisconnected { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([]); + } + + export function serialize( + writer: BinaryWriter, + value: IdentityDisconnected + ): void { + IdentityDisconnected.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): IdentityDisconnected { + return IdentityDisconnected.getTypeScriptAlgebraicType().deserialize( + reader + ); + } +} diff --git a/sdks/typescript/examples/quickstart-chat/src/module_bindings/index.ts b/sdks/typescript/examples/quickstart-chat/src/module_bindings/index.ts new file mode 100644 index 00000000000..8e267b61763 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/module_bindings/index.ts @@ -0,0 +1,276 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; + +// Import and reexport all reducer arg types +import { IdentityConnected } from './identity_connected_reducer.ts'; +export { IdentityConnected }; +import { IdentityDisconnected } from './identity_disconnected_reducer.ts'; +export { IdentityDisconnected }; +import { SendMessage } from './send_message_reducer.ts'; +export { SendMessage }; +import { SetName } from './set_name_reducer.ts'; +export { SetName }; + +// Import and reexport all table handle types +import { MessageTableHandle } from './message_table.ts'; +export { MessageTableHandle }; +import { UserTableHandle } from './user_table.ts'; +export { UserTableHandle }; + +// Import and reexport all types +import { Message } from './message_type.ts'; +export { Message }; +import { User } from './user_type.ts'; +export { User }; + +const REMOTE_MODULE = { + tables: { + message: { + tableName: 'message', + rowType: Message.getTypeScriptAlgebraicType(), + }, + user: { + tableName: 'user', + rowType: User.getTypeScriptAlgebraicType(), + primaryKey: 'identity', + primaryKeyInfo: { + colName: 'identity', + colType: + User.getTypeScriptAlgebraicType().product.elements[0].algebraicType, + }, + }, + }, + reducers: { + identity_connected: { + reducerName: 'identity_connected', + argsType: IdentityConnected.getTypeScriptAlgebraicType(), + }, + identity_disconnected: { + reducerName: 'identity_disconnected', + argsType: IdentityDisconnected.getTypeScriptAlgebraicType(), + }, + send_message: { + reducerName: 'send_message', + argsType: SendMessage.getTypeScriptAlgebraicType(), + }, + set_name: { + reducerName: 'set_name', + argsType: SetName.getTypeScriptAlgebraicType(), + }, + }, + versionInfo: { + cliVersion: '1.2.0', + }, + // Constructors which are used by the DbConnectionImpl to + // extract type information from the generated RemoteModule. + // + // NOTE: This is not strictly necessary for `eventContextConstructor` because + // all we do is build a TypeScript object which we could have done inside the + // SDK, but if in the future we wanted to create a class this would be + // necessary because classes have methods, so we'll keep it. + eventContextConstructor: (imp: DbConnectionImpl, event: Event) => { + return { + ...(imp as DbConnection), + event, + }; + }, + dbViewConstructor: (imp: DbConnectionImpl) => { + return new RemoteTables(imp); + }, + reducersConstructor: ( + imp: DbConnectionImpl, + setReducerFlags: SetReducerFlags + ) => { + return new RemoteReducers(imp, setReducerFlags); + }, + setReducerFlagsConstructor: () => { + return new SetReducerFlags(); + }, +}; + +// A type representing all the possible variants of a reducer. +export type Reducer = + | never + | { name: 'IdentityConnected'; args: IdentityConnected } + | { name: 'IdentityDisconnected'; args: IdentityDisconnected } + | { name: 'SendMessage'; args: SendMessage } + | { name: 'SetName'; args: SetName }; + +export class RemoteReducers { + constructor( + private connection: DbConnectionImpl, + private setCallReducerFlags: SetReducerFlags + ) {} + + onIdentityConnected(callback: (ctx: ReducerEventContext) => void) { + this.connection.onReducer('identity_connected', callback); + } + + removeOnIdentityConnected(callback: (ctx: ReducerEventContext) => void) { + this.connection.offReducer('identity_connected', callback); + } + + onIdentityDisconnected(callback: (ctx: ReducerEventContext) => void) { + this.connection.onReducer('identity_disconnected', callback); + } + + removeOnIdentityDisconnected(callback: (ctx: ReducerEventContext) => void) { + this.connection.offReducer('identity_disconnected', callback); + } + + sendMessage(text: string) { + const __args = { text }; + let __writer = new BinaryWriter(1024); + SendMessage.getTypeScriptAlgebraicType().serialize(__writer, __args); + let __argsBuffer = __writer.getBuffer(); + this.connection.callReducer( + 'send_message', + __argsBuffer, + this.setCallReducerFlags.sendMessageFlags + ); + } + + onSendMessage(callback: (ctx: ReducerEventContext, text: string) => void) { + this.connection.onReducer('send_message', callback); + } + + removeOnSendMessage( + callback: (ctx: ReducerEventContext, text: string) => void + ) { + this.connection.offReducer('send_message', callback); + } + + setName(name: string) { + const __args = { name }; + let __writer = new BinaryWriter(1024); + SetName.getTypeScriptAlgebraicType().serialize(__writer, __args); + let __argsBuffer = __writer.getBuffer(); + this.connection.callReducer( + 'set_name', + __argsBuffer, + this.setCallReducerFlags.setNameFlags + ); + } + + onSetName(callback: (ctx: ReducerEventContext, name: string) => void) { + this.connection.onReducer('set_name', callback); + } + + removeOnSetName(callback: (ctx: ReducerEventContext, name: string) => void) { + this.connection.offReducer('set_name', callback); + } +} + +export class SetReducerFlags { + sendMessageFlags: CallReducerFlags = 'FullUpdate'; + sendMessage(flags: CallReducerFlags) { + this.sendMessageFlags = flags; + } + + setNameFlags: CallReducerFlags = 'FullUpdate'; + setName(flags: CallReducerFlags) { + this.setNameFlags = flags; + } +} + +export class RemoteTables { + constructor(private connection: DbConnectionImpl) {} + + get message(): MessageTableHandle { + return new MessageTableHandle( + this.connection.clientCache.getOrCreateTable( + REMOTE_MODULE.tables.message + ) + ); + } + + get user(): UserTableHandle { + return new UserTableHandle( + this.connection.clientCache.getOrCreateTable( + REMOTE_MODULE.tables.user + ) + ); + } +} + +export class SubscriptionBuilder extends SubscriptionBuilderImpl< + RemoteTables, + RemoteReducers, + SetReducerFlags +> {} + +export class DbConnection extends DbConnectionImpl< + RemoteTables, + RemoteReducers, + SetReducerFlags +> { + static builder = (): DbConnectionBuilder< + DbConnection, + ErrorContext, + SubscriptionEventContext + > => { + return new DbConnectionBuilder< + DbConnection, + ErrorContext, + SubscriptionEventContext + >(REMOTE_MODULE, (imp: DbConnectionImpl) => imp as DbConnection); + }; + subscriptionBuilder = (): SubscriptionBuilder => { + return new SubscriptionBuilder(this); + }; +} + +export type EventContext = EventContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags, + Reducer +>; +export type ReducerEventContext = ReducerEventContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags, + Reducer +>; +export type SubscriptionEventContext = SubscriptionEventContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags +>; +export type ErrorContext = ErrorContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags +>; diff --git a/sdks/typescript/examples/quickstart-chat/src/module_bindings/message_table.ts b/sdks/typescript/examples/quickstart-chat/src/module_bindings/message_table.ts new file mode 100644 index 00000000000..bd410af5126 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/module_bindings/message_table.ts @@ -0,0 +1,83 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; +import { Message } from './message_type'; +import { + type EventContext, + type Reducer, + RemoteReducers, + RemoteTables, +} from '.'; + +/** + * Table handle for the table `message`. + * + * Obtain a handle from the [`message`] property on [`RemoteTables`], + * like `ctx.db.message`. + * + * Users are encouraged not to explicitly reference this type, + * but to directly chain method calls, + * like `ctx.db.message.on_insert(...)`. + */ +export class MessageTableHandle { + tableCache: TableCache; + + constructor(tableCache: TableCache) { + this.tableCache = tableCache; + } + + count(): number { + return this.tableCache.count(); + } + + iter(): Iterable { + return this.tableCache.iter(); + } + + onInsert = (cb: (ctx: EventContext, row: Message) => void) => { + return this.tableCache.onInsert(cb); + }; + + removeOnInsert = (cb: (ctx: EventContext, row: Message) => void) => { + return this.tableCache.removeOnInsert(cb); + }; + + onDelete = (cb: (ctx: EventContext, row: Message) => void) => { + return this.tableCache.onDelete(cb); + }; + + removeOnDelete = (cb: (ctx: EventContext, row: Message) => void) => { + return this.tableCache.removeOnDelete(cb); + }; +} diff --git a/sdks/typescript/examples/quickstart-chat/src/module_bindings/message_type.ts b/sdks/typescript/examples/quickstart-chat/src/module_bindings/message_type.ts new file mode 100644 index 00000000000..c8c0cc87394 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/module_bindings/message_type.ts @@ -0,0 +1,64 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; +export type Message = { + sender: Identity; + sent: Timestamp; + text: string; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace Message { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('sender', AlgebraicType.createIdentityType()), + new ProductTypeElement('sent', AlgebraicType.createTimestampType()), + new ProductTypeElement('text', AlgebraicType.createStringType()), + ]); + } + + export function serialize(writer: BinaryWriter, value: Message): void { + Message.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): Message { + return Message.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/examples/quickstart-chat/src/module_bindings/send_message_reducer.ts b/sdks/typescript/examples/quickstart-chat/src/module_bindings/send_message_reducer.ts new file mode 100644 index 00000000000..988206739fe --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/module_bindings/send_message_reducer.ts @@ -0,0 +1,61 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; + +export type SendMessage = { + text: string; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace SendMessage { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('text', AlgebraicType.createStringType()), + ]); + } + + export function serialize(writer: BinaryWriter, value: SendMessage): void { + SendMessage.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): SendMessage { + return SendMessage.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/examples/quickstart-chat/src/module_bindings/set_name_reducer.ts b/sdks/typescript/examples/quickstart-chat/src/module_bindings/set_name_reducer.ts new file mode 100644 index 00000000000..e1a587cd708 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/module_bindings/set_name_reducer.ts @@ -0,0 +1,61 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; + +export type SetName = { + name: string; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace SetName { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('name', AlgebraicType.createStringType()), + ]); + } + + export function serialize(writer: BinaryWriter, value: SetName): void { + SetName.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): SetName { + return SetName.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/examples/quickstart-chat/src/module_bindings/user_table.ts b/sdks/typescript/examples/quickstart-chat/src/module_bindings/user_table.ts new file mode 100644 index 00000000000..605a14bc304 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/module_bindings/user_table.ts @@ -0,0 +1,116 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; +import { User } from './user_type'; +import { + type EventContext, + type Reducer, + RemoteReducers, + RemoteTables, +} from '.'; + +/** + * Table handle for the table `user`. + * + * Obtain a handle from the [`user`] property on [`RemoteTables`], + * like `ctx.db.user`. + * + * Users are encouraged not to explicitly reference this type, + * but to directly chain method calls, + * like `ctx.db.user.on_insert(...)`. + */ +export class UserTableHandle { + tableCache: TableCache; + + constructor(tableCache: TableCache) { + this.tableCache = tableCache; + } + + count(): number { + return this.tableCache.count(); + } + + iter(): Iterable { + return this.tableCache.iter(); + } + /** + * Access to the `identity` unique index on the table `user`, + * which allows point queries on the field of the same name + * via the [`UserIdentityUnique.find`] method. + * + * Users are encouraged not to explicitly reference this type, + * but to directly chain method calls, + * like `ctx.db.user.identity().find(...)`. + * + * Get a handle on the `identity` unique index on the table `user`. + */ + identity = { + // Find the subscribed row whose `identity` column value is equal to `col_val`, + // if such a row is present in the client cache. + find: (col_val: Identity): User | undefined => { + for (let row of this.tableCache.iter()) { + if (deepEqual(row.identity, col_val)) { + return row; + } + } + }, + }; + + onInsert = (cb: (ctx: EventContext, row: User) => void) => { + return this.tableCache.onInsert(cb); + }; + + removeOnInsert = (cb: (ctx: EventContext, row: User) => void) => { + return this.tableCache.removeOnInsert(cb); + }; + + onDelete = (cb: (ctx: EventContext, row: User) => void) => { + return this.tableCache.onDelete(cb); + }; + + removeOnDelete = (cb: (ctx: EventContext, row: User) => void) => { + return this.tableCache.removeOnDelete(cb); + }; + + // Updates are only defined for tables with primary keys. + onUpdate = (cb: (ctx: EventContext, oldRow: User, newRow: User) => void) => { + return this.tableCache.onUpdate(cb); + }; + + removeOnUpdate = ( + cb: (ctx: EventContext, onRow: User, newRow: User) => void + ) => { + return this.tableCache.removeOnUpdate(cb); + }; +} diff --git a/sdks/typescript/examples/quickstart-chat/src/module_bindings/user_type.ts b/sdks/typescript/examples/quickstart-chat/src/module_bindings/user_type.ts new file mode 100644 index 00000000000..6f11bff36d6 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/module_bindings/user_type.ts @@ -0,0 +1,67 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; +export type User = { + identity: Identity; + name: string | undefined; + online: boolean; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace User { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('identity', AlgebraicType.createIdentityType()), + new ProductTypeElement( + 'name', + AlgebraicType.createOptionType(AlgebraicType.createStringType()) + ), + new ProductTypeElement('online', AlgebraicType.createBoolType()), + ]); + } + + export function serialize(writer: BinaryWriter, value: User): void { + User.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): User { + return User.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/examples/quickstart-chat/src/setupTests.ts b/sdks/typescript/examples/quickstart-chat/src/setupTests.ts new file mode 100644 index 00000000000..7b0828bfa80 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/setupTests.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/sdks/typescript/examples/quickstart-chat/src/vite-env.d.ts b/sdks/typescript/examples/quickstart-chat/src/vite-env.d.ts new file mode 100644 index 00000000000..11f02fe2a00 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/sdks/typescript/examples/quickstart-chat/tsconfig.app.json b/sdks/typescript/examples/quickstart-chat/tsconfig.app.json new file mode 100644 index 00000000000..358ca9ba93f --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/sdks/typescript/examples/quickstart-chat/tsconfig.json b/sdks/typescript/examples/quickstart-chat/tsconfig.json new file mode 100644 index 00000000000..1ffef600d95 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/sdks/typescript/examples/quickstart-chat/tsconfig.node.json b/sdks/typescript/examples/quickstart-chat/tsconfig.node.json new file mode 100644 index 00000000000..db0becc8b03 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/sdks/typescript/examples/quickstart-chat/vite.config.ts b/sdks/typescript/examples/quickstart-chat/vite.config.ts new file mode 100644 index 00000000000..4a5def4c3d7 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}); diff --git a/sdks/typescript/examples/quickstart-chat/vitest.config.ts b/sdks/typescript/examples/quickstart-chat/vitest.config.ts new file mode 100644 index 00000000000..150380175e1 --- /dev/null +++ b/sdks/typescript/examples/quickstart-chat/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + test: { + globals: true, + environment: 'jsdom', // or "node" if you're not testing DOM + setupFiles: './src/setupTests.ts', + testTimeout: 30_000, // give extra time for real connections + hookTimeout: 30_000, + }, +}); diff --git a/sdks/typescript/package.json b/sdks/typescript/package.json new file mode 100644 index 00000000000..85428c188b4 --- /dev/null +++ b/sdks/typescript/package.json @@ -0,0 +1,28 @@ +{ + "name": "spacetimedb-ts-sdk", + "private": true, + "engines": { + "node": ">=18.0.0", + "pnpm": ">=9.0.0" + }, + "scripts": { + "compile": "cd packages/sdk && pnpm compile", + "changeset": "changeset", + "ci:release": "changeset publish", + "ci:version": "changeset version", + "format": "prettier --write .", + "lint": "prettier . --check", + "test": "pnpm -r test" + }, + "devDependencies": { + "@changesets/changelog-github": "^0.5.0", + "@changesets/cli": "^2.27.7", + "brotli-size-cli": "^1.0.0", + "prettier": "^3.3.3", + "terser": "^5.31.2", + "tsup": "^8.1.0", + "tsx": "^4.17.0", + "typescript": "^5.5.3", + "vitest": "^2.0.3" + } +} diff --git a/sdks/typescript/packages/sdk/CHANGELOG.md b/sdks/typescript/packages/sdk/CHANGELOG.md new file mode 100644 index 00000000000..787f1c64640 --- /dev/null +++ b/sdks/typescript/packages/sdk/CHANGELOG.md @@ -0,0 +1,90 @@ +# Changelog + +## 1.2.0 + +### Patch Changes + +- [#176](https://github.com/clockworklabs/spacetimedb-typescript-sdk/pull/176) [`941cf4e`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/941cf4eba6b7df934d74696b373b89cc62764673) Thanks [@BastianGanze](https://github.com/BastianGanze)! - Make ws connection fail when token is invalid + +## 1.0.0-rc1.0 + +### Major Changes + +- [#116](https://github.com/clockworklabs/spacetimedb-typescript-sdk/pull/116) [`9032269`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/9032269004d4dae587c39ccd85da0a32fb9a0114) Thanks [@PuruVJ](https://github.com/PuruVJ)! - Enter RC + +- [#117](https://github.com/clockworklabs/spacetimedb-typescript-sdk/pull/117) [`5d7304b`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/5d7304bd3e05dd7a032cfb7069aab97b881f0179) Thanks [@PuruVJ](https://github.com/PuruVJ)! - feat: Switch to GZIP compression by default + +### Minor Changes + +- [#110](https://github.com/clockworklabs/spacetimedb-typescript-sdk/pull/110) [`a501f5c`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/a501f5ccf9a0a926eb4f345ddeb01ffcb872d67e) Thanks [@Centril](https://github.com/Centril)! - Support light tx updates via builder.with*light_mode(*) and the call flag NoSuccessNotify + +- [#119](https://github.com/clockworklabs/spacetimedb-typescript-sdk/pull/119) [`6547882`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/6547882bb28ed9a1ca436335745e9997328026ff) Thanks [@kazimuth](https://github.com/kazimuth)! - Update Identity and Address to use bigints rather than byte arrays (see https://github.com/clockworklabs/SpacetimeDB/pull/1616) + +### Patch Changes + +- [#109](https://github.com/clockworklabs/spacetimedb-typescript-sdk/pull/109) [`cf7b7d8`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/cf7b7d89a1547fb3863f6641f5b2eb40a27c05d8) Thanks [@PuruVJ](https://github.com/PuruVJ)! - fix: websocket message handling, Buffer, onConnect + +## 0.12.1 + +### Patch Changes + +- [#107](https://github.com/clockworklabs/spacetimedb-typescript-sdk/pull/107) [`2f6c82c`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/2f6c82c724b9f9407c7bedee13252ca8ffab8f7d) Thanks [@PuruVJ](https://github.com/PuruVJ)! - fix: websocket message handling, Buffer, onConnect + +- [#108](https://github.com/clockworklabs/spacetimedb-typescript-sdk/pull/108) [`b9db9b6`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/b9db9b6e46d8c98b29327d97c12c07b7a2fc96bf) Thanks [@PuruVJ](https://github.com/PuruVJ)! - docs: Public facing docs for 0.12 + +- [#105](https://github.com/clockworklabs/spacetimedb-typescript-sdk/pull/105) [`79c278b`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/79c278be71b2dfd82106ada983fd81d395b1d912) Thanks [@PuruVJ](https://github.com/PuruVJ)! - fix: temporary token path invocation + +## 0.12.0 + +### Minor Changes + +- [#92](https://github.com/clockworklabs/spacetimedb-typescript-sdk/pull/92) [`ab1f463`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/ab1f463d7da6e530a6cd47e2433141bfd16addd1) Thanks [@PuruVJ](https://github.com/PuruVJ)! - breaking: Flatten AlgebraicType & Simplify some codegen + +- [#102](https://github.com/clockworklabs/spacetimedb-typescript-sdk/pull/102) [`b8c944c`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/b8c944cd23d3b53c72131803a775127bf0a95213) Thanks [@cloutiertyler](https://github.com/cloutiertyler)! - internal: Remove global instance, allow multiple connections + +### Patch Changes + +- [#91](https://github.com/clockworklabs/spacetimedb-typescript-sdk/pull/91) [`5adb557`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/5adb55776c81d0760cf0268df0fa5dee600f0ef8) Thanks [@PuruVJ](https://github.com/PuruVJ)! - types: Allow autocomplete in .on and .off types + +- [#96](https://github.com/clockworklabs/spacetimedb-typescript-sdk/pull/96) [`17227c0`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/17227c0f65def3a9d5e767756ccf46777210041a) Thanks [@PuruVJ](https://github.com/PuruVJ)! - (fix) Synchronous WS Processing + +## [0.8.0](https://github.com/clockworklabs/spacetimedb-typescript-sdk/compare/0.7.2...0.8.0) (2023-12-11) + +### Bug Fixes + +- Properly use BigInt for any numbers bigger than 32 bits +- Fix generating primary key names to be camel case + +### Features + +- Added ability to start multiple SpacetimeDB clients. New clients will have a separate ClientDB +- Changed the return type of functions returning table records - now they are arras instead of iterators +- Reducer callbacks have args passed in separately, which makes it easier to know what types they are + For example a reducer taking a single string argument will have a callback signature like `(reducerEvent: ReducerEvent, name: string)` + instead of `(reducerEvent: ReducerEvent, args: any[])` +- We now require explicitly registering any tables or reducers with `SpacetimeDBClient.registerReducers()` and `SpacetimeDBClient.registerTables()`. + This also allows to register child classes, which in turn allows to use customized table classes. We will add more info + on how to do it in the future. This makes it also harder to run into weird issues. If you only import a reducer, but not use + it to set any callbacks, Node.js will filter out the import. If you then subscribe to a table SpacetimeDBClient will be unable + to find the reducer. To ensure this is not happening people were adding a `console.log` statement listing and used classes to + stop Node.js from filtering out any imports, like `console.log(SayHelloReducer)`. Now with the reducer call it's more explicit +- In this release we have also moved some methods from generated types into the SDK, which should result in a smaller footprint from + generated classes +- Generated sum types are now easier to use. For sum types without any values you can use their type name as value, for example given an + enum in Rust: + + ```rust + enum UserRole { + Admin, + Moderator, + User, + Other(String) + } + ``` + + you can now use types itself as values. For example given a reducer for setting a role you could now do the following in TypeScript: + + ```typescript + SetRoleReducer.call(UserRole.Admin); + SetRoleReducer.call(UserRole.Other('another role')); + ``` diff --git a/sdks/typescript/packages/sdk/LICENSE.txt b/sdks/typescript/packages/sdk/LICENSE.txt new file mode 100644 index 00000000000..4a459866a57 --- /dev/null +++ b/sdks/typescript/packages/sdk/LICENSE.txt @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/sdks/typescript/packages/sdk/README.md b/sdks/typescript/packages/sdk/README.md new file mode 100644 index 00000000000..884c2a92623 --- /dev/null +++ b/sdks/typescript/packages/sdk/README.md @@ -0,0 +1,73 @@ +## SpacetimeDB SDK + +### Overview + +This repository contains the TypeScript SDK for SpacetimeDB. The SDK allows to interact with the database server and is prepared to work with code generated from a SpacetimeDB backend code. + +### Installation + +The SDK is an NPM package, thus you can use your package manager of choice like NPM or Yarn, for example: + +``` +npm install --save @clockworklabs/spacetimedb-sdk +``` + +You can use the package in the browser, using a bundler like vite/parcel/rsbuild, in server-side applications like NodeJS, Deno, Bun and in Cloudflare Workers. + +> NOTE: For usage in NodeJS 18-21, you need to install the `undici` package as a peer dependency: `npm install @clockworklabs/spacetimedb-sdk undici`. Node 22 and later are supported out of the box. + +### Usage + +In order to connect to a database you have to generate module bindings for your database. + +```ts +import { DbConnection } from './module_bindings'; + +const connection = DbConnection.builder() + .withUri('ws://localhost:3000') + .withModuleName('MODULE_NAME') + .onDisconnect(() => { + console.log('disconnected'); + }) + .onConnectError(() => { + console.log('client_error'); + }) + .onConnect((connection, identity, _token) => { + console.log( + 'Connected to SpacetimeDB with identity:', + identity.toHexString() + ); + + connection.subscriptionBuilder().subscribe('SELECT * FROM player'); + }) + .withToken('TOKEN') + .build(); +``` + +If for some reason you need to disconnect the client: + +```ts +connection.disconnect(); +``` + +Typically, you will use the SDK with types generated from a backend DB service. For example, given a table named `Player` you can subscribe to player updates like this: + +```ts +connection.db.player.onInsert((ctx, player) => { + console.log(player); +}); +``` + +Given a reducer called `CreatePlayer` you can call it using a call method: + +```ts +connection.reducers.createPlayer(); +``` + +### Developer notes + +To run the tests, do: + +```sh +pnpm compile && pnpm test +``` diff --git a/sdks/typescript/packages/sdk/package.json b/sdks/typescript/packages/sdk/package.json new file mode 100644 index 00000000000..6b675b00371 --- /dev/null +++ b/sdks/typescript/packages/sdk/package.json @@ -0,0 +1,52 @@ +{ + "name": "@clockworklabs/spacetimedb-sdk", + "version": "1.2.2", + "description": "SDK for SpacetimeDB", + "author": { + "name": "Clockwork Labs", + "email": "no-reply@clockworklabs.io" + }, + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "module": "./dist/index.js", + "browser": "./dist/browser/index.js", + "type": "module", + "files": [ + "dist" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "browser": "./dist/browser/index.js", + "import": "./dist/index.js", + "default": "./dist/index.js" + }, + "./package.json": "./package.json" + }, + "scripts": { + "compile": "tsup", + "test": "vitest run", + "size": "brotli-size dist/min/index.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/clockworklabs/spacetimedb-typescript-sdk" + }, + "publishConfig": { + "provenance": true + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "undici": "^6.19.2" + }, + "devDependencies": { + "@clockworklabs/test-app": "file:../test-app", + "tsup": "^8.1.0", + "undici": "^6.19.2" + }, + "dependencies": { + "base64-js": "^1.5.1" + } +} diff --git a/sdks/typescript/packages/sdk/src/algebraic_type.ts b/sdks/typescript/packages/sdk/src/algebraic_type.ts new file mode 100644 index 00000000000..0c9846fedeb --- /dev/null +++ b/sdks/typescript/packages/sdk/src/algebraic_type.ts @@ -0,0 +1,672 @@ +import { TimeDuration } from './time_duration'; +import { Timestamp } from './timestamp'; +import { ConnectionId } from './connection_id'; +import type BinaryReader from './binary_reader'; +import BinaryWriter from './binary_writer'; +import { Identity } from './identity'; +import ScheduleAt from './schedule_at'; + +/** + * A variant of a sum type. + * + * NOTE: Each element has an implicit element tag based on its order. + * Uniquely identifies an element similarly to protobuf tags. + */ +export class SumTypeVariant { + name: string; + algebraicType: AlgebraicType; + + constructor(name: string, algebraicType: AlgebraicType) { + this.name = name; + this.algebraicType = algebraicType; + } +} + +/** + * Unlike most languages, sums in SATS are *[structural]* and not nominal. + * When checking whether two nominal types are the same, + * their names and/or declaration sites (e.g., module / namespace) are considered. + * Meanwhile, a structural type system would only check the structure of the type itself, + * e.g., the names of its variants and their inner data types in the case of a sum. + * + * This is also known as a discriminated union (implementation) or disjoint union. + * Another name is [coproduct (category theory)](https://ncatlab.org/nlab/show/coproduct). + * + * These structures are known as sum types because the number of possible values a sum + * ```ignore + * { N_0(T_0), N_1(T_1), ..., N_n(T_n) } + * ``` + * is: + * ```ignore + * Σ (i ∈ 0..n). values(T_i) + * ``` + * so for example, `values({ A(U64), B(Bool) }) = values(U64) + values(Bool)`. + * + * See also: https://ncatlab.org/nlab/show/sum+type. + * + * [structural]: https://en.wikipedia.org/wiki/Structural_type_system + */ +export class SumType { + variants: SumTypeVariant[]; + + constructor(variants: SumTypeVariant[]) { + this.variants = variants; + } + + serialize = (writer: BinaryWriter, value: any): void => { + // In TypeScript we handle Option values as a special case + // we don't represent the some and none variants, but instead + // we represent the value directly. + if ( + this.variants.length == 2 && + this.variants[0].name === 'some' && + this.variants[1].name === 'none' + ) { + if (value !== null && value !== undefined) { + writer.writeByte(0); + this.variants[0].algebraicType.serialize(writer, value); + } else { + writer.writeByte(1); + } + } else { + let variant = value['tag']; + const index = this.variants.findIndex(v => v.name === variant); + if (index < 0) { + throw `Can't serialize a sum type, couldn't find ${value.tag} tag`; + } + writer.writeU8(index); + this.variants[index].algebraicType.serialize(writer, value['value']); + } + }; + + deserialize = (reader: BinaryReader): any => { + let tag = reader.readU8(); + // In TypeScript we handle Option values as a special case + // we don't represent the some and none variants, but instead + // we represent the value directly. + if ( + this.variants.length == 2 && + this.variants[0].name === 'some' && + this.variants[1].name === 'none' + ) { + if (tag === 0) { + return this.variants[0].algebraicType.deserialize(reader); + } else if (tag === 1) { + return undefined; + } else { + throw `Can't deserialize an option type, couldn't find ${tag} tag`; + } + } else { + let variant = this.variants[tag]; + let value = variant.algebraicType.deserialize(reader); + return { tag: variant.name, value }; + } + }; +} + +/** + * A factor / element of a product type. + * + * An element consist of an optional name and a type. + * + * NOTE: Each element has an implicit element tag based on its order. + * Uniquely identifies an element similarly to protobuf tags. + */ +export class ProductTypeElement { + name: string; + algebraicType: AlgebraicType; + + constructor(name: string, algebraicType: AlgebraicType) { + this.name = name; + this.algebraicType = algebraicType; + } +} + +/** + * A structural product type of the factors given by `elements`. + * + * This is also known as `struct` and `tuple` in many languages, + * but note that unlike most languages, products in SATs are *[structural]* and not nominal. + * When checking whether two nominal types are the same, + * their names and/or declaration sites (e.g., module / namespace) are considered. + * Meanwhile, a structural type system would only check the structure of the type itself, + * e.g., the names of its fields and their types in the case of a record. + * The name "product" comes from category theory. + * + * See also: https://ncatlab.org/nlab/show/product+type. + * + * These structures are known as product types because the number of possible values in product + * ```ignore + * { N_0: T_0, N_1: T_1, ..., N_n: T_n } + * ``` + * is: + * ```ignore + * Π (i ∈ 0..n). values(T_i) + * ``` + * so for example, `values({ A: U64, B: Bool }) = values(U64) * values(Bool)`. + * + * [structural]: https://en.wikipedia.org/wiki/Structural_type_system + */ +export class ProductType { + elements: ProductTypeElement[]; + + constructor(elements: ProductTypeElement[]) { + this.elements = elements; + } + + isEmpty(): boolean { + return this.elements.length === 0; + } + + serialize = (writer: BinaryWriter, value: object): void => { + for (let element of this.elements) { + element.algebraicType.serialize(writer, value[element.name]); + } + }; + + intoMapKey(value: any): ComparablePrimitive { + if (this.elements.length === 1) { + if (this.elements[0].name === '__time_duration_micros__') { + return (value as TimeDuration).__time_duration_micros__; + } + + if (this.elements[0].name === '__timestamp_micros_since_unix_epoch__') { + return (value as Timestamp).__timestamp_micros_since_unix_epoch__; + } + + if (this.elements[0].name === '__identity__') { + return (value as Identity).__identity__; + } + + if (this.elements[0].name === '__connection_id__') { + return (value as ConnectionId).__connection_id__; + } + } + // The fallback is to serialize and base64 encode the bytes. + const writer = new BinaryWriter(10); + this.serialize(writer, value); + return writer.toBase64(); + } + + deserialize = (reader: BinaryReader): { [key: string]: any } => { + let result: { [key: string]: any } = {}; + if (this.elements.length === 1) { + if (this.elements[0].name === '__time_duration_micros__') { + return new TimeDuration(reader.readI64()); + } + + if (this.elements[0].name === '__timestamp_micros_since_unix_epoch__') { + return new Timestamp(reader.readI64()); + } + + if (this.elements[0].name === '__identity__') { + return new Identity(reader.readU256()); + } + + if (this.elements[0].name === '__connection_id__') { + return new ConnectionId(reader.readU128()); + } + } + + for (let element of this.elements) { + result[element.name] = element.algebraicType.deserialize(reader); + } + return result; + }; +} + +/* A map type from keys of type `keyType` to values of type `valueType`. */ +export class MapType { + keyType: AlgebraicType; + valueType: AlgebraicType; + + constructor(keyType: AlgebraicType, valueType: AlgebraicType) { + this.keyType = keyType; + this.valueType = valueType; + } +} + +type ArrayBaseType = AlgebraicType; +type TypeRef = null; +type None = null; +export type EnumLabel = { label: string }; + +type AnyType = + | ProductType + | SumType + | ArrayBaseType + | MapType + | EnumLabel + | TypeRef + | None; + +export type ComparablePrimitive = number | string | String | boolean | bigint; + +/** + * The SpacetimeDB Algebraic Type System (SATS) is a structural type system in + * which a nominal type system can be constructed. + * + * The type system unifies the concepts sum types, product types, and built-in + * primitive types into a single type system. + */ +export class AlgebraicType { + type!: Type; + type_?: AnyType; + + #setter(type: Type, payload: AnyType | undefined) { + this.type_ = payload; + this.type = payload === undefined ? Type.None : type; + } + + get product(): ProductType { + if (this.type !== Type.ProductType) { + throw 'product type was requested, but the type is not ProductType'; + } + return this.type_ as ProductType; + } + + set product(value: ProductType | undefined) { + this.#setter(Type.ProductType, value); + } + + get sum(): SumType { + if (this.type !== Type.SumType) { + throw 'sum type was requested, but the type is not SumType'; + } + return this.type_ as SumType; + } + set sum(value: SumType | undefined) { + this.#setter(Type.SumType, value); + } + + get array(): ArrayBaseType { + if (this.type !== Type.ArrayType) { + throw 'array type was requested, but the type is not ArrayType'; + } + return this.type_ as ArrayBaseType; + } + set array(value: ArrayBaseType | undefined) { + this.#setter(Type.ArrayType, value); + } + + get map(): MapType { + if (this.type !== Type.MapType) { + throw 'map type was requested, but the type is not MapType'; + } + return this.type_ as MapType; + } + set map(value: MapType | undefined) { + this.#setter(Type.MapType, value); + } + + static #createType(type: Type, payload: AnyType | undefined): AlgebraicType { + let at = new AlgebraicType(); + at.#setter(type, payload); + return at; + } + + static createProductType(elements: ProductTypeElement[]): AlgebraicType { + return this.#createType(Type.ProductType, new ProductType(elements)); + } + + static createSumType(variants: SumTypeVariant[]): AlgebraicType { + return this.#createType(Type.SumType, new SumType(variants)); + } + + static createArrayType(elementType: AlgebraicType): AlgebraicType { + return this.#createType(Type.ArrayType, elementType); + } + + static createMapType(key: AlgebraicType, val: AlgebraicType): AlgebraicType { + return this.#createType(Type.MapType, new MapType(key, val)); + } + + static createBoolType(): AlgebraicType { + return this.#createType(Type.Bool, null); + } + static createI8Type(): AlgebraicType { + return this.#createType(Type.I8, null); + } + static createU8Type(): AlgebraicType { + return this.#createType(Type.U8, null); + } + static createI16Type(): AlgebraicType { + return this.#createType(Type.I16, null); + } + static createU16Type(): AlgebraicType { + return this.#createType(Type.U16, null); + } + static createI32Type(): AlgebraicType { + return this.#createType(Type.I32, null); + } + static createU32Type(): AlgebraicType { + return this.#createType(Type.U32, null); + } + static createI64Type(): AlgebraicType { + return this.#createType(Type.I64, null); + } + static createU64Type(): AlgebraicType { + return this.#createType(Type.U64, null); + } + static createI128Type(): AlgebraicType { + return this.#createType(Type.I128, null); + } + static createU128Type(): AlgebraicType { + return this.#createType(Type.U128, null); + } + static createI256Type(): AlgebraicType { + return this.#createType(Type.I256, null); + } + static createU256Type(): AlgebraicType { + return this.#createType(Type.U256, null); + } + static createF32Type(): AlgebraicType { + return this.#createType(Type.F32, null); + } + static createF64Type(): AlgebraicType { + return this.#createType(Type.F64, null); + } + static createStringType(): AlgebraicType { + return this.#createType(Type.String, null); + } + static createBytesType(): AlgebraicType { + return this.createArrayType(this.createU8Type()); + } + static createOptionType(innerType: AlgebraicType): AlgebraicType { + return this.createSumType([ + new SumTypeVariant('some', innerType), + new SumTypeVariant('none', this.createProductType([])), + ]); + } + static createIdentityType(): AlgebraicType { + return this.createProductType([ + new ProductTypeElement('__identity__', this.createU256Type()), + ]); + } + + static createConnectionIdType(): AlgebraicType { + return this.createProductType([ + new ProductTypeElement('__connection_id__', this.createU128Type()), + ]); + } + + static createScheduleAtType(): AlgebraicType { + return ScheduleAt.getAlgebraicType(); + } + + static createTimestampType(): AlgebraicType { + return this.createProductType([ + new ProductTypeElement( + '__timestamp_micros_since_unix_epoch__', + this.createI64Type() + ), + ]); + } + + static createTimeDurationType(): AlgebraicType { + return this.createProductType([ + new ProductTypeElement('__time_duration_micros__', this.createI64Type()), + ]); + } + + isProductType(): boolean { + return this.type === Type.ProductType; + } + + isSumType(): boolean { + return this.type === Type.SumType; + } + + isArrayType(): boolean { + return this.type === Type.ArrayType; + } + + isMapType(): boolean { + return this.type === Type.MapType; + } + + #isBytes(): boolean { + return this.isArrayType() && this.array.type == Type.U8; + } + + #isBytesNewtype(tag: string): boolean { + return ( + this.isProductType() && + this.product.elements.length === 1 && + (this.product.elements[0].algebraicType.type == Type.U128 || + this.product.elements[0].algebraicType.type == Type.U256) && + this.product.elements[0].name === tag + ); + } + + #isI64Newtype(tag: string): boolean { + return ( + this.isProductType() && + this.product.elements.length === 1 && + this.product.elements[0].algebraicType.type === Type.I64 && + this.product.elements[0].name === tag + ); + } + + isIdentity(): boolean { + return this.#isBytesNewtype('__identity__'); + } + + isConnectionId(): boolean { + return this.#isBytesNewtype('__connection_id__'); + } + + isScheduleAt(): boolean { + return ( + this.isSumType() && + this.sum.variants.length === 2 && + this.sum.variants[0].name === 'Interval' && + this.sum.variants[0].algebraicType.type === Type.U64 && + this.sum.variants[1].name === 'Time' && + this.sum.variants[1].algebraicType.type === Type.U64 + ); + } + + isTimestamp(): boolean { + return this.#isI64Newtype('__timestamp_micros_since_unix_epoch__'); + } + + isTimeDuration(): boolean { + return this.#isI64Newtype('__time_duration_micros__'); + } + + /** + * Convert a value of the algebraic type into something that can be used as a key in a map. + * There are no guarantees about being able to order it. + * This is only guaranteed to be comparable to other values of the same type. + * @param value A value of the algebraic type + * @returns Something that can be used as a key in a map. + */ + intoMapKey(value: any): ComparablePrimitive { + switch (this.type) { + case Type.U8: + case Type.U16: + case Type.U32: + case Type.U64: + case Type.U128: + case Type.U256: + case Type.I8: + case Type.I16: + case Type.I64: + case Type.I128: + case Type.F32: + case Type.F64: + case Type.String: + case Type.Bool: + return value; + case Type.ProductType: + return this.product.intoMapKey(value); + default: + const writer = new BinaryWriter(10); + this.serialize(writer, value); + return writer.toBase64(); + } + } + + serialize(writer: BinaryWriter, value: any): void { + switch (this.type) { + case Type.ProductType: + this.product.serialize(writer, value); + break; + case Type.SumType: + this.sum.serialize(writer, value); + break; + case Type.ArrayType: + if (this.#isBytes()) { + writer.writeUInt8Array(value); + } else { + const elemType = this.array; + writer.writeU32(value.length); + for (let elem of value) { + elemType.serialize(writer, elem); + } + } + break; + case Type.MapType: + throw new Error('not implemented'); + case Type.Bool: + writer.writeBool(value); + break; + case Type.I8: + writer.writeI8(value); + break; + case Type.U8: + writer.writeU8(value); + break; + case Type.I16: + writer.writeI16(value); + break; + case Type.U16: + writer.writeU16(value); + break; + case Type.I32: + writer.writeI32(value); + break; + case Type.U32: + writer.writeU32(value); + break; + case Type.I64: + writer.writeI64(value); + break; + case Type.U64: + writer.writeU64(value); + break; + case Type.I128: + writer.writeI128(value); + break; + case Type.U128: + writer.writeU128(value); + break; + case Type.I256: + writer.writeI256(value); + break; + case Type.U256: + writer.writeU256(value); + break; + case Type.F32: + writer.writeF32(value); + break; + case Type.F64: + writer.writeF64(value); + break; + case Type.String: + writer.writeString(value); + break; + default: + throw new Error(`not implemented, ${this.type}`); + } + } + + deserialize(reader: BinaryReader): any { + switch (this.type) { + case Type.ProductType: + return this.product.deserialize(reader); + case Type.SumType: + return this.sum.deserialize(reader); + case Type.ArrayType: + if (this.#isBytes()) { + return reader.readUInt8Array(); + } else { + const elemType = this.array; + const length = reader.readU32(); + let result: any[] = []; + for (let i = 0; i < length; i++) { + result.push(elemType.deserialize(reader)); + } + return result; + } + case Type.MapType: + // TODO: MapType is being removed + throw new Error('not implemented'); + case Type.Bool: + return reader.readBool(); + case Type.I8: + return reader.readI8(); + case Type.U8: + return reader.readU8(); + case Type.I16: + return reader.readI16(); + case Type.U16: + return reader.readU16(); + case Type.I32: + return reader.readI32(); + case Type.U32: + return reader.readU32(); + case Type.I64: + return reader.readI64(); + case Type.U64: + return reader.readU64(); + case Type.I128: + return reader.readI128(); + case Type.U128: + return reader.readU128(); + case Type.U256: + return reader.readU256(); + case Type.F32: + return reader.readF32(); + case Type.F64: + return reader.readF64(); + case Type.String: + return reader.readString(); + default: + throw new Error(`not implemented, ${this.type}`); + } + } +} + +export namespace AlgebraicType { + export enum Type { + SumType = 'SumType', + ProductType = 'ProductType', + ArrayType = 'ArrayType', + MapType = 'MapType', + Bool = 'Bool', + I8 = 'I8', + U8 = 'U8', + I16 = 'I16', + U16 = 'U16', + I32 = 'I32', + U32 = 'U32', + I64 = 'I64', + U64 = 'U64', + I128 = 'I128', + U128 = 'U128', + I256 = 'I256', + U256 = 'U256', + F32 = 'F32', + F64 = 'F64', + /** UTF-8 encoded */ + String = 'String', + None = 'None', + } +} + +// No idea why but in order to have a local alias for both of these +// need to be present +type Type = AlgebraicType.Type; +let Type: typeof AlgebraicType.Type = AlgebraicType.Type; diff --git a/sdks/typescript/packages/sdk/src/algebraic_value.ts b/sdks/typescript/packages/sdk/src/algebraic_value.ts new file mode 100644 index 00000000000..b91a1de5a51 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/algebraic_value.ts @@ -0,0 +1,331 @@ +import { ConnectionId } from './connection_id'; +import { AlgebraicType, ProductType, SumType } from './algebraic_type'; +import BinaryReader from './binary_reader'; +import { Identity } from './identity'; +import { ScheduleAt } from './schedule_at'; + +export interface ReducerArgsAdapter { + next: () => ValueAdapter; +} + +export class BinaryReducerArgsAdapter { + adapter: BinaryAdapter; + + constructor(adapter: BinaryAdapter) { + this.adapter = adapter; + } + + next(): ValueAdapter { + return this.adapter; + } +} + +/** Defines the interface for deserialize `AlgebraicValue`s*/ +export interface ValueAdapter { + readUInt8Array: () => Uint8Array; + readArray: (type: AlgebraicType) => AlgebraicValue[]; + readMap: (keyType: AlgebraicType, valueType: AlgebraicType) => MapValue; + readString: () => string; + readSum: (type: SumType) => SumValue; + readProduct: (type: ProductType) => ProductValue; + + readBool: () => boolean; + readByte: () => number; + readI8: () => number; + readU8: () => number; + readI16: () => number; + readU16: () => number; + readI32: () => number; + readU32: () => number; + readI64: () => bigint; + readU64: () => bigint; + readU128: () => bigint; + readI128: () => bigint; + readF32: () => number; + readF64: () => number; + + callMethod(methodName: K): any; +} + +export class BinaryAdapter implements ValueAdapter { + #reader: BinaryReader; + + constructor(reader: BinaryReader) { + this.#reader = reader; + } + + callMethod(methodName: K): any { + return (this[methodName] as Function)(); + } + + readUInt8Array(): Uint8Array { + return this.#reader.readUInt8Array(); + } + + readArray(type: AlgebraicType): AlgebraicValue[] { + const length = this.#reader.readU32(); + let result: AlgebraicValue[] = []; + for (let i = 0; i < length; i++) { + result.push(AlgebraicValue.deserialize(type, this)); + } + + return result; + } + + readMap(keyType: AlgebraicType, valueType: AlgebraicType): MapValue { + const mapLength = this.#reader.readU32(); + let result: MapValue = new Map(); + for (let i = 0; i < mapLength; i++) { + const key = AlgebraicValue.deserialize(keyType, this); + const value = AlgebraicValue.deserialize(valueType, this); + result.set(key, value); + } + + return result; + } + + readString(): string { + return this.#reader.readString(); + } + + readSum(type: SumType): SumValue { + let tag = this.#reader.readByte(); + let sumValue = AlgebraicValue.deserialize( + type.variants[tag].algebraicType, + this + ); + return new SumValue(tag, sumValue); + } + + readProduct(type: ProductType): ProductValue { + let elements: AlgebraicValue[] = []; + + for (let element of type.elements) { + elements.push(AlgebraicValue.deserialize(element.algebraicType, this)); + } + return new ProductValue(elements); + } + + readBool(): boolean { + return this.#reader.readBool(); + } + readByte(): number { + return this.#reader.readByte(); + } + readI8(): number { + return this.#reader.readI8(); + } + readU8(): number { + return this.#reader.readU8(); + } + readI16(): number { + return this.#reader.readI16(); + } + readU16(): number { + return this.#reader.readU16(); + } + readI32(): number { + return this.#reader.readI32(); + } + readU32(): number { + return this.#reader.readU32(); + } + readI64(): bigint { + return this.#reader.readI64(); + } + readU64(): bigint { + return this.#reader.readU64(); + } + readU128(): bigint { + return this.#reader.readU128(); + } + readI128(): bigint { + return this.#reader.readI128(); + } + readF32(): number { + return this.#reader.readF32(); + } + readF64(): number { + return this.#reader.readF64(); + } +} + +/** A value of a sum type choosing a specific variant of the type. */ +export class SumValue { + /** A tag representing the choice of one variant of the sum type's variants. */ + tag: number; + /** + * Given a variant `Var(Ty)` in a sum type `{ Var(Ty), ... }`, + * this provides the `value` for `Ty`. + */ + value: AlgebraicValue; + + constructor(tag: number, value: AlgebraicValue) { + this.tag = tag; + this.value = value; + } + + static deserialize(type: SumType, adapter: ValueAdapter): SumValue { + return adapter.readSum(type); + } +} + +/** + * A product value is made of a list of + * "elements" / "fields" / "factors" of other `AlgebraicValue`s. + * + * The type of product value is a [product type](`ProductType`). + */ +export class ProductValue { + elements: AlgebraicValue[]; + + constructor(elements: AlgebraicValue[]) { + this.elements = elements; + } + + static deserialize(type: ProductType, adapter: ValueAdapter): ProductValue { + return adapter.readProduct(type); + } +} + +export type MapValue = Map; + +type AnyValue = + | SumValue + | ProductValue + | AlgebraicValue[] + | Uint8Array + | MapValue + | string + | boolean + | number + | bigint; + +/** A value in SATS. */ +export class AlgebraicValue { + value: AnyValue; + + constructor(value: AnyValue | undefined) { + if (value === undefined) { + // TODO: possibly get rid of it + throw 'value is undefined'; + } + this.value = value; + } + + callMethod(methodName: K): any { + return (this[methodName] as Function)(); + } + + static deserialize( + type: AlgebraicType, + adapter: ValueAdapter + ): AlgebraicValue { + switch (type.type) { + case AlgebraicType.Type.ProductType: + return new this(ProductValue.deserialize(type.product, adapter)); + case AlgebraicType.Type.SumType: + return new this(SumValue.deserialize(type.sum, adapter)); + case AlgebraicType.Type.ArrayType: + let elemType = type.array; + if (elemType.type === AlgebraicType.Type.U8) { + return new this(adapter.readUInt8Array()); + } else { + return new this(adapter.readArray(elemType)); + } + case AlgebraicType.Type.MapType: + let mapType = type.map; + return new this(adapter.readMap(mapType.keyType, mapType.valueType)); + case AlgebraicType.Type.Bool: + return new this(adapter.readBool()); + case AlgebraicType.Type.I8: + return new this(adapter.readI8()); + case AlgebraicType.Type.U8: + return new this(adapter.readU8()); + case AlgebraicType.Type.I16: + return new this(adapter.readI16()); + case AlgebraicType.Type.U16: + return new this(adapter.readU16()); + case AlgebraicType.Type.I32: + return new this(adapter.readI32()); + case AlgebraicType.Type.U32: + return new this(adapter.readU32()); + case AlgebraicType.Type.I64: + return new this(adapter.readI64()); + case AlgebraicType.Type.U64: + return new this(adapter.readU64()); + case AlgebraicType.Type.I128: + return new this(adapter.readI128()); + case AlgebraicType.Type.U128: + return new this(adapter.readU128()); + case AlgebraicType.Type.String: + return new this(adapter.readString()); + default: + throw new Error(`not implemented, ${type.type}`); + } + } + + // TODO: all of the following methods should actually check the type of `self.value` + // and throw if it does not match. + + asProductValue(): ProductValue { + return this.value as ProductValue; + } + + asField(index: number): AlgebraicValue { + return this.asProductValue().elements[index]; + } + + asSumValue(): SumValue { + return this.value as SumValue; + } + + asArray(): AlgebraicValue[] { + return this.value as AlgebraicValue[]; + } + + asMap(): MapValue { + return this.value as MapValue; + } + + asString(): string { + return this.value as string; + } + + asBoolean(): boolean { + return this.value as boolean; + } + + asNumber(): number { + return this.value as number; + } + + asBytes(): Uint8Array { + return this.value as Uint8Array; + } + + asBigInt(): bigint { + return this.value as bigint; + } + + asIdentity(): Identity { + return new Identity(this.asField(0).asBigInt()); + } + + asConnectionId(): ConnectionId { + return new ConnectionId(this.asField(0).asBigInt()); + } + + asScheduleAt(): ScheduleAt { + return ScheduleAt.fromValue(this); + } +} + +export interface ParseableType { + deserialize: (reader: BinaryReader) => T; +} + +export function parseValue(ty: ParseableType, src: Uint8Array): T { + const reader = new BinaryReader(src); + return ty.deserialize(reader); +} diff --git a/sdks/typescript/packages/sdk/src/binary_reader.ts b/sdks/typescript/packages/sdk/src/binary_reader.ts new file mode 100644 index 00000000000..f40f4101bca --- /dev/null +++ b/sdks/typescript/packages/sdk/src/binary_reader.ts @@ -0,0 +1,165 @@ +export default class BinaryReader { + #buffer: DataView; + #offset: number = 0; + + constructor(input: Uint8Array) { + this.#buffer = new DataView(input.buffer); + this.#offset = input.byteOffset; + } + + get offset(): number { + return this.#offset; + } + + readUInt8Array(): Uint8Array { + const length = this.readU32(); + const value: Uint8Array = new Uint8Array( + this.#buffer.buffer, + this.#offset, + length + ); + this.#offset += length; + return value; + } + + readBool(): boolean { + const value = this.#buffer.getUint8(this.#offset); + this.#offset += 1; + return value !== 0; + } + + readByte(): number { + const value = this.#buffer.getUint8(this.#offset); + this.#offset += 1; + return value; + } + + readBytes(length: number): Uint8Array { + const value: DataView = new DataView( + this.#buffer.buffer, + this.#offset, + length + ); + this.#offset += length; + return new Uint8Array(value.buffer); + } + + readI8(): number { + const value = this.#buffer.getInt8(this.#offset); + this.#offset += 1; + return value; + } + + readU8(): number { + const value = this.#buffer.getUint8(this.#offset); + this.#offset += 1; + return value; + } + + readI16(): number { + const value = this.#buffer.getInt16(this.#offset, true); + this.#offset += 2; + return value; + } + + readU16(): number { + const value = this.#buffer.getUint16(this.#offset, true); + this.#offset += 2; + return value; + } + + readI32(): number { + const value = this.#buffer.getInt32(this.#offset, true); + this.#offset += 4; + return value; + } + + readU32(): number { + const value = this.#buffer.getUint32(this.#offset, true); + this.#offset += 4; + return value; + } + + readI64(): bigint { + const value = this.#buffer.getBigInt64(this.#offset, true); + this.#offset += 8; + return value; + } + + readU64(): bigint { + const value = this.#buffer.getBigUint64(this.#offset, true); + this.#offset += 8; + return value; + } + + readU128(): bigint { + const lowerPart = this.#buffer.getBigUint64(this.#offset, true); + const upperPart = this.#buffer.getBigUint64(this.#offset + 8, true); + this.#offset += 16; + + return (upperPart << BigInt(64)) + lowerPart; + } + + readI128(): bigint { + const lowerPart = this.#buffer.getBigUint64(this.#offset, true); + const upperPart = this.#buffer.getBigInt64(this.#offset + 8, true); + this.#offset += 16; + + return (upperPart << BigInt(64)) + lowerPart; + } + + readU256(): bigint { + const p0 = this.#buffer.getBigUint64(this.#offset, true); + const p1 = this.#buffer.getBigUint64(this.#offset + 8, true); + const p2 = this.#buffer.getBigUint64(this.#offset + 16, true); + const p3 = this.#buffer.getBigUint64(this.#offset + 24, true); + this.#offset += 32; + + return ( + (p3 << BigInt(3 * 64)) + + (p2 << BigInt(2 * 64)) + + (p1 << BigInt(1 * 64)) + + p0 + ); + } + + readI256(): bigint { + const p0 = this.#buffer.getBigUint64(this.#offset, true); + const p1 = this.#buffer.getBigUint64(this.#offset + 8, true); + const p2 = this.#buffer.getBigUint64(this.#offset + 16, true); + const p3 = this.#buffer.getBigInt64(this.#offset + 24, true); + this.#offset += 32; + + return ( + (p3 << BigInt(3 * 64)) + + (p2 << BigInt(2 * 64)) + + (p1 << BigInt(1 * 64)) + + p0 + ); + } + + readF32(): number { + const value = this.#buffer.getFloat32(this.#offset, true); + this.#offset += 4; + return value; + } + + readF64(): number { + const value = this.#buffer.getFloat64(this.#offset, true); + this.#offset += 8; + return value; + } + + readString(): string { + const length = this.readU32(); + const uint8Array = new Uint8Array( + this.#buffer.buffer, + this.#offset, + length + ); + const decoder = new TextDecoder('utf-8'); + const value = decoder.decode(uint8Array); + this.#offset += length; + return value; + } +} diff --git a/sdks/typescript/packages/sdk/src/binary_writer.ts b/sdks/typescript/packages/sdk/src/binary_writer.ts new file mode 100644 index 00000000000..8a55a3ffd44 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/binary_writer.ts @@ -0,0 +1,168 @@ +import { fromByteArray } from 'base64-js'; + +export default class BinaryWriter { + #buffer: Uint8Array; + #view: DataView; + #offset: number = 0; + + constructor(size: number) { + this.#buffer = new Uint8Array(size); + this.#view = new DataView(this.#buffer.buffer); + } + + #expandBuffer(additionalCapacity: number): void { + const minCapacity = this.#offset + additionalCapacity + 1; + if (minCapacity <= this.#buffer.length) return; + let newCapacity = this.#buffer.length * 2; + if (newCapacity < minCapacity) newCapacity = minCapacity; + const newBuffer = new Uint8Array(newCapacity); + newBuffer.set(this.#buffer); + this.#buffer = newBuffer; + this.#view = new DataView(this.#buffer.buffer); + } + + toBase64(): string { + return fromByteArray(this.#buffer.subarray(0, this.#offset)); + } + + getBuffer(): Uint8Array { + return this.#buffer.slice(0, this.#offset); + } + + writeUInt8Array(value: Uint8Array): void { + const length = value.length; + + this.#expandBuffer(4 + length); + + this.writeU32(length); + this.#buffer.set(value, this.#offset); + this.#offset += value.length; + } + + writeBool(value: boolean): void { + this.#expandBuffer(1); + this.#view.setUint8(this.#offset, value ? 1 : 0); + this.#offset += 1; + } + + writeByte(value: number): void { + this.#expandBuffer(1); + this.#view.setUint8(this.#offset, value); + this.#offset += 1; + } + + writeI8(value: number): void { + this.#expandBuffer(1); + this.#view.setInt8(this.#offset, value); + this.#offset += 1; + } + + writeU8(value: number): void { + this.#expandBuffer(1); + this.#view.setUint8(this.#offset, value); + this.#offset += 1; + } + + writeI16(value: number): void { + this.#expandBuffer(2); + this.#view.setInt16(this.#offset, value, true); + this.#offset += 2; + } + + writeU16(value: number): void { + this.#expandBuffer(2); + this.#view.setUint16(this.#offset, value, true); + this.#offset += 2; + } + + writeI32(value: number): void { + this.#expandBuffer(4); + this.#view.setInt32(this.#offset, value, true); + this.#offset += 4; + } + + writeU32(value: number): void { + this.#expandBuffer(4); + this.#view.setUint32(this.#offset, value, true); + this.#offset += 4; + } + + writeI64(value: bigint): void { + this.#expandBuffer(8); + this.#view.setBigInt64(this.#offset, value, true); + this.#offset += 8; + } + + writeU64(value: bigint): void { + this.#expandBuffer(8); + this.#view.setBigUint64(this.#offset, value, true); + this.#offset += 8; + } + + writeU128(value: bigint): void { + this.#expandBuffer(16); + const lowerPart = value & BigInt('0xFFFFFFFFFFFFFFFF'); + const upperPart = value >> BigInt(64); + this.#view.setBigUint64(this.#offset, lowerPart, true); + this.#view.setBigUint64(this.#offset + 8, upperPart, true); + this.#offset += 16; + } + + writeI128(value: bigint): void { + this.#expandBuffer(16); + const lowerPart = value & BigInt('0xFFFFFFFFFFFFFFFF'); + const upperPart = value >> BigInt(64); + this.#view.setBigInt64(this.#offset, lowerPart, true); + this.#view.setBigInt64(this.#offset + 8, upperPart, true); + this.#offset += 16; + } + + writeU256(value: bigint): void { + this.#expandBuffer(32); + const low_64_mask = BigInt('0xFFFFFFFFFFFFFFFF'); + const p0 = value & low_64_mask; + const p1 = (value >> BigInt(64 * 1)) & low_64_mask; + const p2 = (value >> BigInt(64 * 2)) & low_64_mask; + const p3 = value >> BigInt(64 * 3); + this.#view.setBigUint64(this.#offset + 8 * 0, p0, true); + this.#view.setBigUint64(this.#offset + 8 * 1, p1, true); + this.#view.setBigUint64(this.#offset + 8 * 2, p2, true); + this.#view.setBigUint64(this.#offset + 8 * 3, p3, true); + this.#offset += 32; + } + + writeI256(value: bigint): void { + this.#expandBuffer(32); + const low_64_mask = BigInt('0xFFFFFFFFFFFFFFFF'); + const p0 = value & low_64_mask; + const p1 = (value >> BigInt(64 * 1)) & low_64_mask; + const p2 = (value >> BigInt(64 * 2)) & low_64_mask; + const p3 = value >> BigInt(64 * 3); + this.#view.setBigUint64(this.#offset + 8 * 0, p0, true); + this.#view.setBigUint64(this.#offset + 8 * 1, p1, true); + this.#view.setBigUint64(this.#offset + 8 * 2, p2, true); + this.#view.setBigInt64(this.#offset + 8 * 3, p3, true); + this.#offset += 32; + } + + writeF32(value: number): void { + this.#expandBuffer(4); + this.#view.setFloat32(this.#offset, value, true); + this.#offset += 4; + } + + writeF64(value: number): void { + this.#expandBuffer(8); + this.#view.setFloat64(this.#offset, value, true); + this.#offset += 8; + } + + writeString(value: string): void { + const encoder = new TextEncoder(); + const encodedString = encoder.encode(value); + this.writeU32(encodedString.length); + this.#expandBuffer(encodedString.length); + this.#buffer.set(encodedString, this.#offset); + this.#offset += encodedString.length; + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/bsatn_row_list_type.ts b/sdks/typescript/packages/sdk/src/client_api/bsatn_row_list_type.ts new file mode 100644 index 00000000000..800837cc879 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/bsatn_row_list_type.ts @@ -0,0 +1,68 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { RowSizeHint as __RowSizeHint } from './row_size_hint_type'; + +export type BsatnRowList = { + sizeHint: __RowSizeHint; + rowsData: Uint8Array; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace BsatnRowList { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement( + 'sizeHint', + __RowSizeHint.getTypeScriptAlgebraicType() + ), + new ProductTypeElement( + 'rowsData', + AlgebraicType.createArrayType(AlgebraicType.createU8Type()) + ), + ]); + } + + export function serialize(writer: BinaryWriter, value: BsatnRowList): void { + BsatnRowList.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): BsatnRowList { + return BsatnRowList.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/call_reducer_type.ts b/sdks/typescript/packages/sdk/src/client_api/call_reducer_type.ts new file mode 100644 index 00000000000..d3bc69d59e3 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/call_reducer_type.ts @@ -0,0 +1,67 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +export type CallReducer = { + reducer: string; + args: Uint8Array; + requestId: number; + flags: number; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace CallReducer { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('reducer', AlgebraicType.createStringType()), + new ProductTypeElement( + 'args', + AlgebraicType.createArrayType(AlgebraicType.createU8Type()) + ), + new ProductTypeElement('requestId', AlgebraicType.createU32Type()), + new ProductTypeElement('flags', AlgebraicType.createU8Type()), + ]); + } + + export function serialize(writer: BinaryWriter, value: CallReducer): void { + CallReducer.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): CallReducer { + return CallReducer.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/client_message_type.ts b/sdks/typescript/packages/sdk/src/client_api/client_message_type.ts new file mode 100644 index 00000000000..024fb466d5a --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/client_message_type.ts @@ -0,0 +1,146 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { CallReducer as __CallReducer } from './call_reducer_type'; +import { Subscribe as __Subscribe } from './subscribe_type'; +import { OneOffQuery as __OneOffQuery } from './one_off_query_type'; +import { SubscribeSingle as __SubscribeSingle } from './subscribe_single_type'; +import { SubscribeMulti as __SubscribeMulti } from './subscribe_multi_type'; +import { Unsubscribe as __Unsubscribe } from './unsubscribe_type'; +import { UnsubscribeMulti as __UnsubscribeMulti } from './unsubscribe_multi_type'; + +// A namespace for generated variants and helper functions. +export namespace ClientMessage { + // These are the generated variant types for each variant of the tagged union. + // One type is generated per variant and will be used in the `value` field of + // the tagged union. + export type CallReducer = { tag: 'CallReducer'; value: __CallReducer }; + export type Subscribe = { tag: 'Subscribe'; value: __Subscribe }; + export type OneOffQuery = { tag: 'OneOffQuery'; value: __OneOffQuery }; + export type SubscribeSingle = { + tag: 'SubscribeSingle'; + value: __SubscribeSingle; + }; + export type SubscribeMulti = { + tag: 'SubscribeMulti'; + value: __SubscribeMulti; + }; + export type Unsubscribe = { tag: 'Unsubscribe'; value: __Unsubscribe }; + export type UnsubscribeMulti = { + tag: 'UnsubscribeMulti'; + value: __UnsubscribeMulti; + }; + + // Helper functions for constructing each variant of the tagged union. + // ``` + // const foo = Foo.A(42); + // assert!(foo.tag === "A"); + // assert!(foo.value === 42); + // ``` + export const CallReducer = (value: __CallReducer): ClientMessage => ({ + tag: 'CallReducer', + value, + }); + export const Subscribe = (value: __Subscribe): ClientMessage => ({ + tag: 'Subscribe', + value, + }); + export const OneOffQuery = (value: __OneOffQuery): ClientMessage => ({ + tag: 'OneOffQuery', + value, + }); + export const SubscribeSingle = (value: __SubscribeSingle): ClientMessage => ({ + tag: 'SubscribeSingle', + value, + }); + export const SubscribeMulti = (value: __SubscribeMulti): ClientMessage => ({ + tag: 'SubscribeMulti', + value, + }); + export const Unsubscribe = (value: __Unsubscribe): ClientMessage => ({ + tag: 'Unsubscribe', + value, + }); + export const UnsubscribeMulti = ( + value: __UnsubscribeMulti + ): ClientMessage => ({ tag: 'UnsubscribeMulti', value }); + + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createSumType([ + new SumTypeVariant( + 'CallReducer', + __CallReducer.getTypeScriptAlgebraicType() + ), + new SumTypeVariant('Subscribe', __Subscribe.getTypeScriptAlgebraicType()), + new SumTypeVariant( + 'OneOffQuery', + __OneOffQuery.getTypeScriptAlgebraicType() + ), + new SumTypeVariant( + 'SubscribeSingle', + __SubscribeSingle.getTypeScriptAlgebraicType() + ), + new SumTypeVariant( + 'SubscribeMulti', + __SubscribeMulti.getTypeScriptAlgebraicType() + ), + new SumTypeVariant( + 'Unsubscribe', + __Unsubscribe.getTypeScriptAlgebraicType() + ), + new SumTypeVariant( + 'UnsubscribeMulti', + __UnsubscribeMulti.getTypeScriptAlgebraicType() + ), + ]); + } + + export function serialize(writer: BinaryWriter, value: ClientMessage): void { + ClientMessage.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): ClientMessage { + return ClientMessage.getTypeScriptAlgebraicType().deserialize(reader); + } +} + +// The tagged union or sum type for the algebraic type `ClientMessage`. +export type ClientMessage = + | ClientMessage.CallReducer + | ClientMessage.Subscribe + | ClientMessage.OneOffQuery + | ClientMessage.SubscribeSingle + | ClientMessage.SubscribeMulti + | ClientMessage.Unsubscribe + | ClientMessage.UnsubscribeMulti; + +export default ClientMessage; diff --git a/sdks/typescript/packages/sdk/src/client_api/compressable_query_update_type.ts b/sdks/typescript/packages/sdk/src/client_api/compressable_query_update_type.ts new file mode 100644 index 00000000000..a1727419171 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/compressable_query_update_type.ts @@ -0,0 +1,102 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { QueryUpdate as __QueryUpdate } from './query_update_type'; + +// A namespace for generated variants and helper functions. +export namespace CompressableQueryUpdate { + // These are the generated variant types for each variant of the tagged union. + // One type is generated per variant and will be used in the `value` field of + // the tagged union. + export type Uncompressed = { tag: 'Uncompressed'; value: __QueryUpdate }; + export type Brotli = { tag: 'Brotli'; value: Uint8Array }; + export type Gzip = { tag: 'Gzip'; value: Uint8Array }; + + // Helper functions for constructing each variant of the tagged union. + // ``` + // const foo = Foo.A(42); + // assert!(foo.tag === "A"); + // assert!(foo.value === 42); + // ``` + export const Uncompressed = ( + value: __QueryUpdate + ): CompressableQueryUpdate => ({ tag: 'Uncompressed', value }); + export const Brotli = (value: Uint8Array): CompressableQueryUpdate => ({ + tag: 'Brotli', + value, + }); + export const Gzip = (value: Uint8Array): CompressableQueryUpdate => ({ + tag: 'Gzip', + value, + }); + + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createSumType([ + new SumTypeVariant( + 'Uncompressed', + __QueryUpdate.getTypeScriptAlgebraicType() + ), + new SumTypeVariant( + 'Brotli', + AlgebraicType.createArrayType(AlgebraicType.createU8Type()) + ), + new SumTypeVariant( + 'Gzip', + AlgebraicType.createArrayType(AlgebraicType.createU8Type()) + ), + ]); + } + + export function serialize( + writer: BinaryWriter, + value: CompressableQueryUpdate + ): void { + CompressableQueryUpdate.getTypeScriptAlgebraicType().serialize( + writer, + value + ); + } + + export function deserialize(reader: BinaryReader): CompressableQueryUpdate { + return CompressableQueryUpdate.getTypeScriptAlgebraicType().deserialize( + reader + ); + } +} + +// The tagged union or sum type for the algebraic type `CompressableQueryUpdate`. +export type CompressableQueryUpdate = + | CompressableQueryUpdate.Uncompressed + | CompressableQueryUpdate.Brotli + | CompressableQueryUpdate.Gzip; + +export default CompressableQueryUpdate; diff --git a/sdks/typescript/packages/sdk/src/client_api/database_update_type.ts b/sdks/typescript/packages/sdk/src/client_api/database_update_type.ts new file mode 100644 index 00000000000..3a133f50b69 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/database_update_type.ts @@ -0,0 +1,65 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { TableUpdate as __TableUpdate } from './table_update_type'; + +export type DatabaseUpdate = { + tables: __TableUpdate[]; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace DatabaseUpdate { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement( + 'tables', + AlgebraicType.createArrayType( + __TableUpdate.getTypeScriptAlgebraicType() + ) + ), + ]); + } + + export function serialize(writer: BinaryWriter, value: DatabaseUpdate): void { + DatabaseUpdate.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): DatabaseUpdate { + return DatabaseUpdate.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/energy_quanta_type.ts b/sdks/typescript/packages/sdk/src/client_api/energy_quanta_type.ts new file mode 100644 index 00000000000..686ca504d84 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/energy_quanta_type.ts @@ -0,0 +1,58 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +export type EnergyQuanta = { + quanta: bigint; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace EnergyQuanta { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('quanta', AlgebraicType.createU128Type()), + ]); + } + + export function serialize(writer: BinaryWriter, value: EnergyQuanta): void { + EnergyQuanta.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): EnergyQuanta { + return EnergyQuanta.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/identity_token_type.ts b/sdks/typescript/packages/sdk/src/client_api/identity_token_type.ts new file mode 100644 index 00000000000..11dd455da03 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/identity_token_type.ts @@ -0,0 +1,65 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +export type IdentityToken = { + identity: Identity; + token: string; + connectionId: ConnectionId; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace IdentityToken { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('identity', AlgebraicType.createIdentityType()), + new ProductTypeElement('token', AlgebraicType.createStringType()), + new ProductTypeElement( + 'connectionId', + AlgebraicType.createConnectionIdType() + ), + ]); + } + + export function serialize(writer: BinaryWriter, value: IdentityToken): void { + IdentityToken.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): IdentityToken { + return IdentityToken.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/index.ts b/sdks/typescript/packages/sdk/src/client_api/index.ts new file mode 100644 index 00000000000..4274ff8c8e1 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/index.ts @@ -0,0 +1,196 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; + +// Import and reexport all reducer arg types + +// Import and reexport all table handle types + +// Import and reexport all types +import { BsatnRowList } from './bsatn_row_list_type.ts'; +export { BsatnRowList }; +import { CallReducer } from './call_reducer_type.ts'; +export { CallReducer }; +import { ClientMessage } from './client_message_type.ts'; +export { ClientMessage }; +import { CompressableQueryUpdate } from './compressable_query_update_type.ts'; +export { CompressableQueryUpdate }; +import { DatabaseUpdate } from './database_update_type.ts'; +export { DatabaseUpdate }; +import { EnergyQuanta } from './energy_quanta_type.ts'; +export { EnergyQuanta }; +import { IdentityToken } from './identity_token_type.ts'; +export { IdentityToken }; +import { InitialSubscription } from './initial_subscription_type.ts'; +export { InitialSubscription }; +import { OneOffQuery } from './one_off_query_type.ts'; +export { OneOffQuery }; +import { OneOffQueryResponse } from './one_off_query_response_type.ts'; +export { OneOffQueryResponse }; +import { OneOffTable } from './one_off_table_type.ts'; +export { OneOffTable }; +import { QueryId } from './query_id_type.ts'; +export { QueryId }; +import { QueryUpdate } from './query_update_type.ts'; +export { QueryUpdate }; +import { ReducerCallInfo } from './reducer_call_info_type.ts'; +export { ReducerCallInfo }; +import { RowSizeHint } from './row_size_hint_type.ts'; +export { RowSizeHint }; +import { ServerMessage } from './server_message_type.ts'; +export { ServerMessage }; +import { Subscribe } from './subscribe_type.ts'; +export { Subscribe }; +import { SubscribeApplied } from './subscribe_applied_type.ts'; +export { SubscribeApplied }; +import { SubscribeMulti } from './subscribe_multi_type.ts'; +export { SubscribeMulti }; +import { SubscribeMultiApplied } from './subscribe_multi_applied_type.ts'; +export { SubscribeMultiApplied }; +import { SubscribeRows } from './subscribe_rows_type.ts'; +export { SubscribeRows }; +import { SubscribeSingle } from './subscribe_single_type.ts'; +export { SubscribeSingle }; +import { SubscriptionError } from './subscription_error_type.ts'; +export { SubscriptionError }; +import { TableUpdate } from './table_update_type.ts'; +export { TableUpdate }; +import { TransactionUpdate } from './transaction_update_type.ts'; +export { TransactionUpdate }; +import { TransactionUpdateLight } from './transaction_update_light_type.ts'; +export { TransactionUpdateLight }; +import { Unsubscribe } from './unsubscribe_type.ts'; +export { Unsubscribe }; +import { UnsubscribeApplied } from './unsubscribe_applied_type.ts'; +export { UnsubscribeApplied }; +import { UnsubscribeMulti } from './unsubscribe_multi_type.ts'; +export { UnsubscribeMulti }; +import { UnsubscribeMultiApplied } from './unsubscribe_multi_applied_type.ts'; +export { UnsubscribeMultiApplied }; +import { UpdateStatus } from './update_status_type.ts'; +export { UpdateStatus }; + +const REMOTE_MODULE = { + tables: {}, + reducers: {}, + // Constructors which are used by the DbConnectionImpl to + // extract type information from the generated RemoteModule. + // + // NOTE: This is not strictly necessary for `eventContextConstructor` because + // all we do is build a TypeScript object which we could have done inside the + // SDK, but if in the future we wanted to create a class this would be + // necessary because classes have methods, so we'll keep it. + eventContextConstructor: (imp: DbConnectionImpl, event: Event) => { + return { + ...(imp as DbConnection), + event, + }; + }, + dbViewConstructor: (imp: DbConnectionImpl) => { + return new RemoteTables(imp); + }, + reducersConstructor: ( + imp: DbConnectionImpl, + setReducerFlags: SetReducerFlags + ) => { + return new RemoteReducers(imp, setReducerFlags); + }, + setReducerFlagsConstructor: () => { + return new SetReducerFlags(); + }, +}; + +// A type representing all the possible variants of a reducer. +export type Reducer = never; + +export class RemoteReducers { + constructor( + private connection: DbConnectionImpl, + private setCallReducerFlags: SetReducerFlags + ) {} +} + +export class SetReducerFlags {} + +export class RemoteTables { + constructor(private connection: DbConnectionImpl) {} +} + +export class SubscriptionBuilder extends SubscriptionBuilderImpl< + RemoteTables, + RemoteReducers, + SetReducerFlags +> {} + +export class DbConnection extends DbConnectionImpl< + RemoteTables, + RemoteReducers, + SetReducerFlags +> { + static builder = (): DbConnectionBuilder< + DbConnection, + ErrorContext, + SubscriptionEventContext + > => { + return new DbConnectionBuilder< + DbConnection, + ErrorContext, + SubscriptionEventContext + >(REMOTE_MODULE, (imp: DbConnectionImpl) => imp as DbConnection); + }; + subscriptionBuilder = (): SubscriptionBuilder => { + return new SubscriptionBuilder(this); + }; +} + +export type EventContext = EventContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags, + Reducer +>; +export type ReducerEventContext = ReducerEventContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags, + Reducer +>; +export type SubscriptionEventContext = SubscriptionEventContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags +>; +export type ErrorContext = ErrorContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags +>; diff --git a/sdks/typescript/packages/sdk/src/client_api/initial_subscription_type.ts b/sdks/typescript/packages/sdk/src/client_api/initial_subscription_type.ts new file mode 100644 index 00000000000..266ac6833e0 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/initial_subscription_type.ts @@ -0,0 +1,73 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { DatabaseUpdate as __DatabaseUpdate } from './database_update_type'; + +export type InitialSubscription = { + databaseUpdate: __DatabaseUpdate; + requestId: number; + totalHostExecutionDuration: TimeDuration; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace InitialSubscription { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement( + 'databaseUpdate', + __DatabaseUpdate.getTypeScriptAlgebraicType() + ), + new ProductTypeElement('requestId', AlgebraicType.createU32Type()), + new ProductTypeElement( + 'totalHostExecutionDuration', + AlgebraicType.createTimeDurationType() + ), + ]); + } + + export function serialize( + writer: BinaryWriter, + value: InitialSubscription + ): void { + InitialSubscription.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): InitialSubscription { + return InitialSubscription.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/one_off_query_response_type.ts b/sdks/typescript/packages/sdk/src/client_api/one_off_query_response_type.ts new file mode 100644 index 00000000000..885f9809361 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/one_off_query_response_type.ts @@ -0,0 +1,83 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { OneOffTable as __OneOffTable } from './one_off_table_type'; + +export type OneOffQueryResponse = { + messageId: Uint8Array; + error: string | undefined; + tables: __OneOffTable[]; + totalHostExecutionDuration: TimeDuration; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace OneOffQueryResponse { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement( + 'messageId', + AlgebraicType.createArrayType(AlgebraicType.createU8Type()) + ), + new ProductTypeElement( + 'error', + AlgebraicType.createOptionType(AlgebraicType.createStringType()) + ), + new ProductTypeElement( + 'tables', + AlgebraicType.createArrayType( + __OneOffTable.getTypeScriptAlgebraicType() + ) + ), + new ProductTypeElement( + 'totalHostExecutionDuration', + AlgebraicType.createTimeDurationType() + ), + ]); + } + + export function serialize( + writer: BinaryWriter, + value: OneOffQueryResponse + ): void { + OneOffQueryResponse.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): OneOffQueryResponse { + return OneOffQueryResponse.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/one_off_query_type.ts b/sdks/typescript/packages/sdk/src/client_api/one_off_query_type.ts new file mode 100644 index 00000000000..8fb2fdc881a --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/one_off_query_type.ts @@ -0,0 +1,63 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +export type OneOffQuery = { + messageId: Uint8Array; + queryString: string; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace OneOffQuery { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement( + 'messageId', + AlgebraicType.createArrayType(AlgebraicType.createU8Type()) + ), + new ProductTypeElement('queryString', AlgebraicType.createStringType()), + ]); + } + + export function serialize(writer: BinaryWriter, value: OneOffQuery): void { + OneOffQuery.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): OneOffQuery { + return OneOffQuery.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/one_off_table_type.ts b/sdks/typescript/packages/sdk/src/client_api/one_off_table_type.ts new file mode 100644 index 00000000000..32c8abb64ba --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/one_off_table_type.ts @@ -0,0 +1,65 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { BsatnRowList as __BsatnRowList } from './bsatn_row_list_type'; + +export type OneOffTable = { + tableName: string; + rows: __BsatnRowList; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace OneOffTable { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('tableName', AlgebraicType.createStringType()), + new ProductTypeElement( + 'rows', + __BsatnRowList.getTypeScriptAlgebraicType() + ), + ]); + } + + export function serialize(writer: BinaryWriter, value: OneOffTable): void { + OneOffTable.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): OneOffTable { + return OneOffTable.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/query_id_type.ts b/sdks/typescript/packages/sdk/src/client_api/query_id_type.ts new file mode 100644 index 00000000000..1f41bf5e3ad --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/query_id_type.ts @@ -0,0 +1,58 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +export type QueryId = { + id: number; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace QueryId { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('id', AlgebraicType.createU32Type()), + ]); + } + + export function serialize(writer: BinaryWriter, value: QueryId): void { + QueryId.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): QueryId { + return QueryId.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/query_update_type.ts b/sdks/typescript/packages/sdk/src/client_api/query_update_type.ts new file mode 100644 index 00000000000..10e0147f047 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/query_update_type.ts @@ -0,0 +1,68 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { BsatnRowList as __BsatnRowList } from './bsatn_row_list_type'; + +export type QueryUpdate = { + deletes: __BsatnRowList; + inserts: __BsatnRowList; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace QueryUpdate { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement( + 'deletes', + __BsatnRowList.getTypeScriptAlgebraicType() + ), + new ProductTypeElement( + 'inserts', + __BsatnRowList.getTypeScriptAlgebraicType() + ), + ]); + } + + export function serialize(writer: BinaryWriter, value: QueryUpdate): void { + QueryUpdate.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): QueryUpdate { + return QueryUpdate.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/reducer_call_info_type.ts b/sdks/typescript/packages/sdk/src/client_api/reducer_call_info_type.ts new file mode 100644 index 00000000000..337fb073e23 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/reducer_call_info_type.ts @@ -0,0 +1,70 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +export type ReducerCallInfo = { + reducerName: string; + reducerId: number; + args: Uint8Array; + requestId: number; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace ReducerCallInfo { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('reducerName', AlgebraicType.createStringType()), + new ProductTypeElement('reducerId', AlgebraicType.createU32Type()), + new ProductTypeElement( + 'args', + AlgebraicType.createArrayType(AlgebraicType.createU8Type()) + ), + new ProductTypeElement('requestId', AlgebraicType.createU32Type()), + ]); + } + + export function serialize( + writer: BinaryWriter, + value: ReducerCallInfo + ): void { + ReducerCallInfo.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): ReducerCallInfo { + return ReducerCallInfo.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/row_size_hint_type.ts b/sdks/typescript/packages/sdk/src/client_api/row_size_hint_type.ts new file mode 100644 index 00000000000..7f6830bcc51 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/row_size_hint_type.ts @@ -0,0 +1,78 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +// A namespace for generated variants and helper functions. +export namespace RowSizeHint { + // These are the generated variant types for each variant of the tagged union. + // One type is generated per variant and will be used in the `value` field of + // the tagged union. + export type FixedSize = { tag: 'FixedSize'; value: number }; + export type RowOffsets = { tag: 'RowOffsets'; value: bigint[] }; + + // Helper functions for constructing each variant of the tagged union. + // ``` + // const foo = Foo.A(42); + // assert!(foo.tag === "A"); + // assert!(foo.value === 42); + // ``` + export const FixedSize = (value: number): RowSizeHint => ({ + tag: 'FixedSize', + value, + }); + export const RowOffsets = (value: bigint[]): RowSizeHint => ({ + tag: 'RowOffsets', + value, + }); + + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createSumType([ + new SumTypeVariant('FixedSize', AlgebraicType.createU16Type()), + new SumTypeVariant( + 'RowOffsets', + AlgebraicType.createArrayType(AlgebraicType.createU64Type()) + ), + ]); + } + + export function serialize(writer: BinaryWriter, value: RowSizeHint): void { + RowSizeHint.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): RowSizeHint { + return RowSizeHint.getTypeScriptAlgebraicType().deserialize(reader); + } +} + +// The tagged union or sum type for the algebraic type `RowSizeHint`. +export type RowSizeHint = RowSizeHint.FixedSize | RowSizeHint.RowOffsets; + +export default RowSizeHint; diff --git a/sdks/typescript/packages/sdk/src/client_api/server_message_type.ts b/sdks/typescript/packages/sdk/src/client_api/server_message_type.ts new file mode 100644 index 00000000000..c88b76f43f1 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/server_message_type.ts @@ -0,0 +1,192 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { InitialSubscription as __InitialSubscription } from './initial_subscription_type'; +import { TransactionUpdate as __TransactionUpdate } from './transaction_update_type'; +import { TransactionUpdateLight as __TransactionUpdateLight } from './transaction_update_light_type'; +import { IdentityToken as __IdentityToken } from './identity_token_type'; +import { OneOffQueryResponse as __OneOffQueryResponse } from './one_off_query_response_type'; +import { SubscribeApplied as __SubscribeApplied } from './subscribe_applied_type'; +import { UnsubscribeApplied as __UnsubscribeApplied } from './unsubscribe_applied_type'; +import { SubscriptionError as __SubscriptionError } from './subscription_error_type'; +import { SubscribeMultiApplied as __SubscribeMultiApplied } from './subscribe_multi_applied_type'; +import { UnsubscribeMultiApplied as __UnsubscribeMultiApplied } from './unsubscribe_multi_applied_type'; + +// A namespace for generated variants and helper functions. +export namespace ServerMessage { + // These are the generated variant types for each variant of the tagged union. + // One type is generated per variant and will be used in the `value` field of + // the tagged union. + export type InitialSubscription = { + tag: 'InitialSubscription'; + value: __InitialSubscription; + }; + export type TransactionUpdate = { + tag: 'TransactionUpdate'; + value: __TransactionUpdate; + }; + export type TransactionUpdateLight = { + tag: 'TransactionUpdateLight'; + value: __TransactionUpdateLight; + }; + export type IdentityToken = { tag: 'IdentityToken'; value: __IdentityToken }; + export type OneOffQueryResponse = { + tag: 'OneOffQueryResponse'; + value: __OneOffQueryResponse; + }; + export type SubscribeApplied = { + tag: 'SubscribeApplied'; + value: __SubscribeApplied; + }; + export type UnsubscribeApplied = { + tag: 'UnsubscribeApplied'; + value: __UnsubscribeApplied; + }; + export type SubscriptionError = { + tag: 'SubscriptionError'; + value: __SubscriptionError; + }; + export type SubscribeMultiApplied = { + tag: 'SubscribeMultiApplied'; + value: __SubscribeMultiApplied; + }; + export type UnsubscribeMultiApplied = { + tag: 'UnsubscribeMultiApplied'; + value: __UnsubscribeMultiApplied; + }; + + // Helper functions for constructing each variant of the tagged union. + // ``` + // const foo = Foo.A(42); + // assert!(foo.tag === "A"); + // assert!(foo.value === 42); + // ``` + export const InitialSubscription = ( + value: __InitialSubscription + ): ServerMessage => ({ tag: 'InitialSubscription', value }); + export const TransactionUpdate = ( + value: __TransactionUpdate + ): ServerMessage => ({ tag: 'TransactionUpdate', value }); + export const TransactionUpdateLight = ( + value: __TransactionUpdateLight + ): ServerMessage => ({ tag: 'TransactionUpdateLight', value }); + export const IdentityToken = (value: __IdentityToken): ServerMessage => ({ + tag: 'IdentityToken', + value, + }); + export const OneOffQueryResponse = ( + value: __OneOffQueryResponse + ): ServerMessage => ({ tag: 'OneOffQueryResponse', value }); + export const SubscribeApplied = ( + value: __SubscribeApplied + ): ServerMessage => ({ tag: 'SubscribeApplied', value }); + export const UnsubscribeApplied = ( + value: __UnsubscribeApplied + ): ServerMessage => ({ tag: 'UnsubscribeApplied', value }); + export const SubscriptionError = ( + value: __SubscriptionError + ): ServerMessage => ({ tag: 'SubscriptionError', value }); + export const SubscribeMultiApplied = ( + value: __SubscribeMultiApplied + ): ServerMessage => ({ tag: 'SubscribeMultiApplied', value }); + export const UnsubscribeMultiApplied = ( + value: __UnsubscribeMultiApplied + ): ServerMessage => ({ tag: 'UnsubscribeMultiApplied', value }); + + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createSumType([ + new SumTypeVariant( + 'InitialSubscription', + __InitialSubscription.getTypeScriptAlgebraicType() + ), + new SumTypeVariant( + 'TransactionUpdate', + __TransactionUpdate.getTypeScriptAlgebraicType() + ), + new SumTypeVariant( + 'TransactionUpdateLight', + __TransactionUpdateLight.getTypeScriptAlgebraicType() + ), + new SumTypeVariant( + 'IdentityToken', + __IdentityToken.getTypeScriptAlgebraicType() + ), + new SumTypeVariant( + 'OneOffQueryResponse', + __OneOffQueryResponse.getTypeScriptAlgebraicType() + ), + new SumTypeVariant( + 'SubscribeApplied', + __SubscribeApplied.getTypeScriptAlgebraicType() + ), + new SumTypeVariant( + 'UnsubscribeApplied', + __UnsubscribeApplied.getTypeScriptAlgebraicType() + ), + new SumTypeVariant( + 'SubscriptionError', + __SubscriptionError.getTypeScriptAlgebraicType() + ), + new SumTypeVariant( + 'SubscribeMultiApplied', + __SubscribeMultiApplied.getTypeScriptAlgebraicType() + ), + new SumTypeVariant( + 'UnsubscribeMultiApplied', + __UnsubscribeMultiApplied.getTypeScriptAlgebraicType() + ), + ]); + } + + export function serialize(writer: BinaryWriter, value: ServerMessage): void { + ServerMessage.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): ServerMessage { + return ServerMessage.getTypeScriptAlgebraicType().deserialize(reader); + } +} + +// The tagged union or sum type for the algebraic type `ServerMessage`. +export type ServerMessage = + | ServerMessage.InitialSubscription + | ServerMessage.TransactionUpdate + | ServerMessage.TransactionUpdateLight + | ServerMessage.IdentityToken + | ServerMessage.OneOffQueryResponse + | ServerMessage.SubscribeApplied + | ServerMessage.UnsubscribeApplied + | ServerMessage.SubscriptionError + | ServerMessage.SubscribeMultiApplied + | ServerMessage.UnsubscribeMultiApplied; + +export default ServerMessage; diff --git a/sdks/typescript/packages/sdk/src/client_api/subscribe_applied_type.ts b/sdks/typescript/packages/sdk/src/client_api/subscribe_applied_type.ts new file mode 100644 index 00000000000..fbd3dfb9c55 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/subscribe_applied_type.ts @@ -0,0 +1,76 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { QueryId as __QueryId } from './query_id_type'; +import { SubscribeRows as __SubscribeRows } from './subscribe_rows_type'; + +export type SubscribeApplied = { + requestId: number; + totalHostExecutionDurationMicros: bigint; + queryId: __QueryId; + rows: __SubscribeRows; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace SubscribeApplied { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('requestId', AlgebraicType.createU32Type()), + new ProductTypeElement( + 'totalHostExecutionDurationMicros', + AlgebraicType.createU64Type() + ), + new ProductTypeElement('queryId', __QueryId.getTypeScriptAlgebraicType()), + new ProductTypeElement( + 'rows', + __SubscribeRows.getTypeScriptAlgebraicType() + ), + ]); + } + + export function serialize( + writer: BinaryWriter, + value: SubscribeApplied + ): void { + SubscribeApplied.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): SubscribeApplied { + return SubscribeApplied.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/subscribe_multi_applied_type.ts b/sdks/typescript/packages/sdk/src/client_api/subscribe_multi_applied_type.ts new file mode 100644 index 00000000000..54b78817959 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/subscribe_multi_applied_type.ts @@ -0,0 +1,78 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { QueryId as __QueryId } from './query_id_type'; +import { DatabaseUpdate as __DatabaseUpdate } from './database_update_type'; + +export type SubscribeMultiApplied = { + requestId: number; + totalHostExecutionDurationMicros: bigint; + queryId: __QueryId; + update: __DatabaseUpdate; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace SubscribeMultiApplied { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('requestId', AlgebraicType.createU32Type()), + new ProductTypeElement( + 'totalHostExecutionDurationMicros', + AlgebraicType.createU64Type() + ), + new ProductTypeElement('queryId', __QueryId.getTypeScriptAlgebraicType()), + new ProductTypeElement( + 'update', + __DatabaseUpdate.getTypeScriptAlgebraicType() + ), + ]); + } + + export function serialize( + writer: BinaryWriter, + value: SubscribeMultiApplied + ): void { + SubscribeMultiApplied.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): SubscribeMultiApplied { + return SubscribeMultiApplied.getTypeScriptAlgebraicType().deserialize( + reader + ); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/subscribe_multi_type.ts b/sdks/typescript/packages/sdk/src/client_api/subscribe_multi_type.ts new file mode 100644 index 00000000000..349f9f1a059 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/subscribe_multi_type.ts @@ -0,0 +1,67 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { QueryId as __QueryId } from './query_id_type'; + +export type SubscribeMulti = { + queryStrings: string[]; + requestId: number; + queryId: __QueryId; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace SubscribeMulti { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement( + 'queryStrings', + AlgebraicType.createArrayType(AlgebraicType.createStringType()) + ), + new ProductTypeElement('requestId', AlgebraicType.createU32Type()), + new ProductTypeElement('queryId', __QueryId.getTypeScriptAlgebraicType()), + ]); + } + + export function serialize(writer: BinaryWriter, value: SubscribeMulti): void { + SubscribeMulti.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): SubscribeMulti { + return SubscribeMulti.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/subscribe_rows_type.ts b/sdks/typescript/packages/sdk/src/client_api/subscribe_rows_type.ts new file mode 100644 index 00000000000..b1c6d6295ea --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/subscribe_rows_type.ts @@ -0,0 +1,67 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { TableUpdate as __TableUpdate } from './table_update_type'; + +export type SubscribeRows = { + tableId: number; + tableName: string; + tableRows: __TableUpdate; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace SubscribeRows { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('tableId', AlgebraicType.createU32Type()), + new ProductTypeElement('tableName', AlgebraicType.createStringType()), + new ProductTypeElement( + 'tableRows', + __TableUpdate.getTypeScriptAlgebraicType() + ), + ]); + } + + export function serialize(writer: BinaryWriter, value: SubscribeRows): void { + SubscribeRows.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): SubscribeRows { + return SubscribeRows.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/subscribe_single_type.ts b/sdks/typescript/packages/sdk/src/client_api/subscribe_single_type.ts new file mode 100644 index 00000000000..2f564678c7e --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/subscribe_single_type.ts @@ -0,0 +1,67 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { QueryId as __QueryId } from './query_id_type'; + +export type SubscribeSingle = { + query: string; + requestId: number; + queryId: __QueryId; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace SubscribeSingle { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('query', AlgebraicType.createStringType()), + new ProductTypeElement('requestId', AlgebraicType.createU32Type()), + new ProductTypeElement('queryId', __QueryId.getTypeScriptAlgebraicType()), + ]); + } + + export function serialize( + writer: BinaryWriter, + value: SubscribeSingle + ): void { + SubscribeSingle.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): SubscribeSingle { + return SubscribeSingle.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/subscribe_type.ts b/sdks/typescript/packages/sdk/src/client_api/subscribe_type.ts new file mode 100644 index 00000000000..cb6e4edb6ad --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/subscribe_type.ts @@ -0,0 +1,63 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +export type Subscribe = { + queryStrings: string[]; + requestId: number; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace Subscribe { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement( + 'queryStrings', + AlgebraicType.createArrayType(AlgebraicType.createStringType()) + ), + new ProductTypeElement('requestId', AlgebraicType.createU32Type()), + ]); + } + + export function serialize(writer: BinaryWriter, value: Subscribe): void { + Subscribe.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): Subscribe { + return Subscribe.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/subscription_error_type.ts b/sdks/typescript/packages/sdk/src/client_api/subscription_error_type.ts new file mode 100644 index 00000000000..d8e0a792ee2 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/subscription_error_type.ts @@ -0,0 +1,81 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +export type SubscriptionError = { + totalHostExecutionDurationMicros: bigint; + requestId: number | undefined; + queryId: number | undefined; + tableId: number | undefined; + error: string; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace SubscriptionError { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement( + 'totalHostExecutionDurationMicros', + AlgebraicType.createU64Type() + ), + new ProductTypeElement( + 'requestId', + AlgebraicType.createOptionType(AlgebraicType.createU32Type()) + ), + new ProductTypeElement( + 'queryId', + AlgebraicType.createOptionType(AlgebraicType.createU32Type()) + ), + new ProductTypeElement( + 'tableId', + AlgebraicType.createOptionType(AlgebraicType.createU32Type()) + ), + new ProductTypeElement('error', AlgebraicType.createStringType()), + ]); + } + + export function serialize( + writer: BinaryWriter, + value: SubscriptionError + ): void { + SubscriptionError.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): SubscriptionError { + return SubscriptionError.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/table_update_type.ts b/sdks/typescript/packages/sdk/src/client_api/table_update_type.ts new file mode 100644 index 00000000000..cc4999cb9fc --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/table_update_type.ts @@ -0,0 +1,71 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { CompressableQueryUpdate as __CompressableQueryUpdate } from './compressable_query_update_type'; + +export type TableUpdate = { + tableId: number; + tableName: string; + numRows: bigint; + updates: __CompressableQueryUpdate[]; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace TableUpdate { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('tableId', AlgebraicType.createU32Type()), + new ProductTypeElement('tableName', AlgebraicType.createStringType()), + new ProductTypeElement('numRows', AlgebraicType.createU64Type()), + new ProductTypeElement( + 'updates', + AlgebraicType.createArrayType( + __CompressableQueryUpdate.getTypeScriptAlgebraicType() + ) + ), + ]); + } + + export function serialize(writer: BinaryWriter, value: TableUpdate): void { + TableUpdate.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): TableUpdate { + return TableUpdate.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/transaction_update_light_type.ts b/sdks/typescript/packages/sdk/src/client_api/transaction_update_light_type.ts new file mode 100644 index 00000000000..567fe4b332a --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/transaction_update_light_type.ts @@ -0,0 +1,73 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { DatabaseUpdate as __DatabaseUpdate } from './database_update_type'; + +export type TransactionUpdateLight = { + requestId: number; + update: __DatabaseUpdate; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace TransactionUpdateLight { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('requestId', AlgebraicType.createU32Type()), + new ProductTypeElement( + 'update', + __DatabaseUpdate.getTypeScriptAlgebraicType() + ), + ]); + } + + export function serialize( + writer: BinaryWriter, + value: TransactionUpdateLight + ): void { + TransactionUpdateLight.getTypeScriptAlgebraicType().serialize( + writer, + value + ); + } + + export function deserialize(reader: BinaryReader): TransactionUpdateLight { + return TransactionUpdateLight.getTypeScriptAlgebraicType().deserialize( + reader + ); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/transaction_update_type.ts b/sdks/typescript/packages/sdk/src/client_api/transaction_update_type.ts new file mode 100644 index 00000000000..6391ba98d6d --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/transaction_update_type.ts @@ -0,0 +1,95 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { UpdateStatus as __UpdateStatus } from './update_status_type'; +import { ReducerCallInfo as __ReducerCallInfo } from './reducer_call_info_type'; +import { EnergyQuanta as __EnergyQuanta } from './energy_quanta_type'; + +export type TransactionUpdate = { + status: __UpdateStatus; + timestamp: Timestamp; + callerIdentity: Identity; + callerConnectionId: ConnectionId; + reducerCall: __ReducerCallInfo; + energyQuantaUsed: __EnergyQuanta; + totalHostExecutionDuration: TimeDuration; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace TransactionUpdate { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement( + 'status', + __UpdateStatus.getTypeScriptAlgebraicType() + ), + new ProductTypeElement('timestamp', AlgebraicType.createTimestampType()), + new ProductTypeElement( + 'callerIdentity', + AlgebraicType.createIdentityType() + ), + new ProductTypeElement( + 'callerConnectionId', + AlgebraicType.createConnectionIdType() + ), + new ProductTypeElement( + 'reducerCall', + __ReducerCallInfo.getTypeScriptAlgebraicType() + ), + new ProductTypeElement( + 'energyQuantaUsed', + __EnergyQuanta.getTypeScriptAlgebraicType() + ), + new ProductTypeElement( + 'totalHostExecutionDuration', + AlgebraicType.createTimeDurationType() + ), + ]); + } + + export function serialize( + writer: BinaryWriter, + value: TransactionUpdate + ): void { + TransactionUpdate.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): TransactionUpdate { + return TransactionUpdate.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/unsubscribe_applied_type.ts b/sdks/typescript/packages/sdk/src/client_api/unsubscribe_applied_type.ts new file mode 100644 index 00000000000..ab63a797d5a --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/unsubscribe_applied_type.ts @@ -0,0 +1,76 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { QueryId as __QueryId } from './query_id_type'; +import { SubscribeRows as __SubscribeRows } from './subscribe_rows_type'; + +export type UnsubscribeApplied = { + requestId: number; + totalHostExecutionDurationMicros: bigint; + queryId: __QueryId; + rows: __SubscribeRows; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace UnsubscribeApplied { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('requestId', AlgebraicType.createU32Type()), + new ProductTypeElement( + 'totalHostExecutionDurationMicros', + AlgebraicType.createU64Type() + ), + new ProductTypeElement('queryId', __QueryId.getTypeScriptAlgebraicType()), + new ProductTypeElement( + 'rows', + __SubscribeRows.getTypeScriptAlgebraicType() + ), + ]); + } + + export function serialize( + writer: BinaryWriter, + value: UnsubscribeApplied + ): void { + UnsubscribeApplied.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): UnsubscribeApplied { + return UnsubscribeApplied.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/unsubscribe_multi_applied_type.ts b/sdks/typescript/packages/sdk/src/client_api/unsubscribe_multi_applied_type.ts new file mode 100644 index 00000000000..92126057962 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/unsubscribe_multi_applied_type.ts @@ -0,0 +1,81 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { QueryId as __QueryId } from './query_id_type'; +import { DatabaseUpdate as __DatabaseUpdate } from './database_update_type'; + +export type UnsubscribeMultiApplied = { + requestId: number; + totalHostExecutionDurationMicros: bigint; + queryId: __QueryId; + update: __DatabaseUpdate; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace UnsubscribeMultiApplied { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('requestId', AlgebraicType.createU32Type()), + new ProductTypeElement( + 'totalHostExecutionDurationMicros', + AlgebraicType.createU64Type() + ), + new ProductTypeElement('queryId', __QueryId.getTypeScriptAlgebraicType()), + new ProductTypeElement( + 'update', + __DatabaseUpdate.getTypeScriptAlgebraicType() + ), + ]); + } + + export function serialize( + writer: BinaryWriter, + value: UnsubscribeMultiApplied + ): void { + UnsubscribeMultiApplied.getTypeScriptAlgebraicType().serialize( + writer, + value + ); + } + + export function deserialize(reader: BinaryReader): UnsubscribeMultiApplied { + return UnsubscribeMultiApplied.getTypeScriptAlgebraicType().deserialize( + reader + ); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/unsubscribe_multi_type.ts b/sdks/typescript/packages/sdk/src/client_api/unsubscribe_multi_type.ts new file mode 100644 index 00000000000..32d02ba14c0 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/unsubscribe_multi_type.ts @@ -0,0 +1,65 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { QueryId as __QueryId } from './query_id_type'; + +export type UnsubscribeMulti = { + requestId: number; + queryId: __QueryId; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace UnsubscribeMulti { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('requestId', AlgebraicType.createU32Type()), + new ProductTypeElement('queryId', __QueryId.getTypeScriptAlgebraicType()), + ]); + } + + export function serialize( + writer: BinaryWriter, + value: UnsubscribeMulti + ): void { + UnsubscribeMulti.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): UnsubscribeMulti { + return UnsubscribeMulti.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/unsubscribe_type.ts b/sdks/typescript/packages/sdk/src/client_api/unsubscribe_type.ts new file mode 100644 index 00000000000..368f68c4d87 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/unsubscribe_type.ts @@ -0,0 +1,62 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { QueryId as __QueryId } from './query_id_type'; + +export type Unsubscribe = { + requestId: number; + queryId: __QueryId; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace Unsubscribe { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('requestId', AlgebraicType.createU32Type()), + new ProductTypeElement('queryId', __QueryId.getTypeScriptAlgebraicType()), + ]); + } + + export function serialize(writer: BinaryWriter, value: Unsubscribe): void { + Unsubscribe.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): Unsubscribe { + return Unsubscribe.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/sdk/src/client_api/update_status_type.ts b/sdks/typescript/packages/sdk/src/client_api/update_status_type.ts new file mode 100644 index 00000000000..68aa8865f54 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_api/update_status_type.ts @@ -0,0 +1,86 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + CallReducerFlags, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + DbContext, + ErrorContextInterface, + Event, + EventContextInterface, + Identity, + ProductType, + ProductTypeElement, + ReducerEventContextInterface, + SubscriptionBuilderImpl, + SubscriptionEventContextInterface, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, +} from '../index'; +import { DatabaseUpdate as __DatabaseUpdate } from './database_update_type'; + +// A namespace for generated variants and helper functions. +export namespace UpdateStatus { + // These are the generated variant types for each variant of the tagged union. + // One type is generated per variant and will be used in the `value` field of + // the tagged union. + export type Committed = { tag: 'Committed'; value: __DatabaseUpdate }; + export type Failed = { tag: 'Failed'; value: string }; + export type OutOfEnergy = { tag: 'OutOfEnergy' }; + + // Helper functions for constructing each variant of the tagged union. + // ``` + // const foo = Foo.A(42); + // assert!(foo.tag === "A"); + // assert!(foo.value === 42); + // ``` + export const Committed = (value: __DatabaseUpdate): UpdateStatus => ({ + tag: 'Committed', + value, + }); + export const Failed = (value: string): UpdateStatus => ({ + tag: 'Failed', + value, + }); + export const OutOfEnergy = { tag: 'OutOfEnergy' }; + + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createSumType([ + new SumTypeVariant( + 'Committed', + __DatabaseUpdate.getTypeScriptAlgebraicType() + ), + new SumTypeVariant('Failed', AlgebraicType.createStringType()), + new SumTypeVariant('OutOfEnergy', AlgebraicType.createProductType([])), + ]); + } + + export function serialize(writer: BinaryWriter, value: UpdateStatus): void { + UpdateStatus.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): UpdateStatus { + return UpdateStatus.getTypeScriptAlgebraicType().deserialize(reader); + } +} + +// The tagged union or sum type for the algebraic type `UpdateStatus`. +export type UpdateStatus = + | UpdateStatus.Committed + | UpdateStatus.Failed + | UpdateStatus.OutOfEnergy; + +export default UpdateStatus; diff --git a/sdks/typescript/packages/sdk/src/client_cache.ts b/sdks/typescript/packages/sdk/src/client_cache.ts new file mode 100644 index 00000000000..94ac8749422 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/client_cache.ts @@ -0,0 +1,45 @@ +import type { TableRuntimeTypeInfo } from './spacetime_module.ts'; +import { TableCache } from './table_cache.ts'; + +export class ClientCache { + /** + * The tables in the database. + */ + tables: Map; + + constructor() { + this.tables = new Map(); + } + + /** + * Returns the table with the given name. + * @param name The name of the table. + * @returns The table + */ + getTable(name: string): TableCache { + const table = this.tables.get(name); + + // ! This should not happen as the table should be available but an exception is thrown just in case. + if (!table) { + console.error( + 'The table has not been registered for this client. Please register the table before using it. If you have registered global tables using the SpacetimeDBClient.registerTables() or `registerTable()` method, please make sure that is executed first!' + ); + throw new Error(`Table ${name} does not exist`); + } + + return table; + } + + getOrCreateTable( + tableTypeInfo: TableRuntimeTypeInfo + ): TableCache { + let table: TableCache; + if (!this.tables.has(tableTypeInfo.tableName)) { + table = new TableCache(tableTypeInfo); + this.tables.set(tableTypeInfo.tableName, table); + } else { + table = this.tables.get(tableTypeInfo.tableName)!; + } + return table; + } +} diff --git a/sdks/typescript/packages/sdk/src/connection_id.ts b/sdks/typescript/packages/sdk/src/connection_id.ts new file mode 100644 index 00000000000..c8d95016c18 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/connection_id.ts @@ -0,0 +1,79 @@ +import { hexStringToU128, u128ToHexString, u128ToUint8Array } from './utils'; + +/** + * A unique identifier for a client connected to a database. + */ +export class ConnectionId { + data: bigint; + + get __connection_id__(): bigint { + return this.data; + } + + /** + * Creates a new `ConnectionId`. + */ + constructor(data: bigint) { + this.data = data; + } + + isZero(): boolean { + return this.data === BigInt(0); + } + + static nullIfZero(addr: ConnectionId): ConnectionId | null { + if (addr.isZero()) { + return null; + } else { + return addr; + } + } + + static random(): ConnectionId { + function randomU8(): number { + return Math.floor(Math.random() * 0xff); + } + let result = BigInt(0); + for (let i = 0; i < 16; i++) { + result = (result << BigInt(8)) | BigInt(randomU8()); + } + return new ConnectionId(result); + } + + /** + * Compare two connection IDs for equality. + */ + isEqual(other: ConnectionId): boolean { + return this.data == other.data; + } + + /** + * Print the connection ID as a hexadecimal string. + */ + toHexString(): string { + return u128ToHexString(this.data); + } + + /** + * Convert the connection ID to a Uint8Array. + */ + toUint8Array(): Uint8Array { + return u128ToUint8Array(this.data); + } + + /** + * Parse a connection ID from a hexadecimal string. + */ + static fromString(str: string): ConnectionId { + return new ConnectionId(hexStringToU128(str)); + } + + static fromStringOrNull(str: string): ConnectionId | null { + let addr = ConnectionId.fromString(str); + if (addr.isZero()) { + return null; + } else { + return addr; + } + } +} diff --git a/sdks/typescript/packages/sdk/src/db_connection_builder.ts b/sdks/typescript/packages/sdk/src/db_connection_builder.ts new file mode 100644 index 00000000000..ed62b93eff0 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/db_connection_builder.ts @@ -0,0 +1,236 @@ +import { DbConnectionImpl, type ConnectionEvent } from './db_connection_impl'; +import { EventEmitter } from './event_emitter'; +import type { Identity } from './identity'; +import type RemoteModule from './spacetime_module'; +import { ensureMinimumVersionOrThrow } from './version'; +import { WebsocketDecompressAdapter } from './websocket_decompress_adapter'; + +/** + * The database client connection to a SpacetimeDB server. + */ +export class DbConnectionBuilder< + DbConnection, + ErrorContext, + SubscriptionEventContext, +> { + #uri?: URL; + #nameOrAddress?: string; + #identity?: Identity; + #token?: string; + #emitter: EventEmitter = new EventEmitter(); + #compression: 'gzip' | 'none' = 'gzip'; + #lightMode: boolean = false; + #createWSFn: typeof WebsocketDecompressAdapter.createWebSocketFn; + + /** + * Creates a new `DbConnectionBuilder` database client and set the initial parameters. + * + * Users are not expected to call this constructor directly. Instead, use the static method `DbConnection.builder()`. + * + * @param remoteModule The remote module to use to connect to the SpacetimeDB server. + * @param dbConnectionConstructor The constructor to use to create a new `DbConnection`. + */ + constructor( + private remoteModule: RemoteModule, + private dbConnectionConstructor: (imp: DbConnectionImpl) => DbConnection + ) { + this.#createWSFn = WebsocketDecompressAdapter.createWebSocketFn; + } + + /** + * Set the URI of the SpacetimeDB server to connect to. + * + * @param uri The URI of the SpacetimeDB server to connect to. + * + **/ + withUri(uri: string | URL): this { + this.#uri = new URL(uri); + return this; + } + + /** + * Set the name or Identity of the database module to connect to. + * + * @param nameOrAddress + * + * @returns The `DbConnectionBuilder` instance. + */ + withModuleName(nameOrAddress: string): this { + this.#nameOrAddress = nameOrAddress; + return this; + } + + /** + * Set the identity of the client to connect to the database. + * + * @param token The credentials to use to authenticate with SpacetimeDB. This + * is optional. You can store the token returned by the `onConnect` callback + * to use in future connections. + * + * @returns The `DbConnectionBuilder` instance. + */ + withToken(token?: string): this { + this.#token = token; + return this; + } + + withWSFn( + createWSFn: (args: { + url: URL; + wsProtocol: string; + authToken?: string; + }) => Promise + ): this { + this.#createWSFn = createWSFn; + return this; + } + + /** + * Set the compression algorithm to use for the connection. + * + * @param compression The compression algorithm to use for the connection. + */ + withCompression(compression: 'gzip' | 'none'): this { + this.#compression = compression; + return this; + } + + /** + * Sets the connection to operate in light mode. + * + * Light mode is a mode that reduces the amount of data sent over the network. + * + * @param lightMode The light mode for the connection. + */ + withLightMode(lightMode: boolean): this { + this.#lightMode = lightMode; + return this; + } + + /** + * Register a callback to be invoked upon authentication with the database. + * + * @param identity A unique identifier for a client connected to a database. + * @param token The credentials to use to authenticate with SpacetimeDB. + * + * @returns The `DbConnectionBuilder` instance. + * + * The callback will be invoked with the `Identity` and private authentication `token` provided by the database to identify this connection. + * + * If credentials were supplied to connect, those passed to the callback will be equivalent to the ones used to connect. + * + * If the initial connection was anonymous, a new set of credentials will be generated by the database to identify this user. + * + * The credentials passed to the callback can be saved and used to authenticate the same user in future connections. + * + * @example + * + * ```ts + * DbConnection.builder().onConnect((ctx, identity, token) => { + * console.log("Connected to SpacetimeDB with identity:", identity.toHexString()); + * }); + * ``` + */ + onConnect( + callback: ( + connection: DbConnection, + identity: Identity, + token: string + ) => void + ): this { + this.#emitter.on('connect', callback); + return this; + } + + /** + * Register a callback to be invoked upon an error. + * + * @example + * + * ```ts + * DbConnection.builder().onConnectError((ctx, error) => { + * console.log("Error connecting to SpacetimeDB:", error); + * }); + * ``` + */ + onConnectError(callback: (ctx: ErrorContext, error: Error) => void): this { + this.#emitter.on('connectError', callback); + return this; + } + + /** + * Registers a callback to run when a {@link DbConnection} whose connection initially succeeded + * is disconnected, either after a {@link DbConnection.disconnect} call or due to an error. + * + * If the connection ended because of an error, the error is passed to the callback. + * + * The `callback` will be installed on the `DbConnection` created by `build` + * before initiating the connection, ensuring there's no opportunity for the disconnect to happen + * before the callback is installed. + * + * Note that this does not trigger if `build` fails + * or in cases where {@link DbConnectionBuilder.onConnectError} would trigger. + * This callback only triggers if the connection closes after `build` returns successfully + * and {@link DbConnectionBuilder.onConnect} is invoked, i.e., after the `IdentityToken` is received. + * + * To simplify SDK implementation, at most one such callback can be registered. + * Calling `onDisconnect` on the same `DbConnectionBuilder` multiple times throws an error. + * + * Unlike callbacks registered via {@link DbConnection}, + * no mechanism is provided to unregister the provided callback. + * This is a concession to ergonomics; there's no clean place to return a `CallbackId` from this method + * or from `build`. + * + * @param {function(error?: Error): void} callback - The callback to invoke upon disconnection. + * @throws {Error} Throws an error if called multiple times on the same `DbConnectionBuilder`. + */ + onDisconnect( + callback: (ctx: ErrorContext, error?: Error | undefined) => void + ): this { + this.#emitter.on('disconnect', callback); + return this; + } + + /** + * Builds a new `DbConnection` with the parameters set on this `DbConnectionBuilder` and attempts to connect to the SpacetimeDB server. + * + * @returns A new `DbConnection` with the parameters set on this `DbConnectionBuilder`. + * + * @example + * + * ```ts + * const host = "http://localhost:3000"; + * const name_or_address = "database_name" + * const auth_token = undefined; + * DbConnection.builder().withUri(host).withModuleName(name_or_address).withToken(auth_token).build(); + * ``` + */ + build(): DbConnection { + if (!this.#uri) { + throw new Error('URI is required to connect to SpacetimeDB'); + } + + if (!this.#nameOrAddress) { + throw new Error( + 'Database name or address is required to connect to SpacetimeDB' + ); + } + // We could consider making this an `onConnectError` instead of throwing here. + // Ideally, it would be a compile time error, but I'm not sure how to accomplish that. + ensureMinimumVersionOrThrow(this.remoteModule.versionInfo?.cliVersion); + + return this.dbConnectionConstructor( + new DbConnectionImpl({ + uri: this.#uri, + nameOrAddress: this.#nameOrAddress, + identity: this.#identity, + token: this.#token, + emitter: this.#emitter, + compression: this.#compression, + lightMode: this.#lightMode, + createWSFn: this.#createWSFn, + remoteModule: this.remoteModule, + }) + ); + } +} diff --git a/sdks/typescript/packages/sdk/src/db_connection_impl.ts b/sdks/typescript/packages/sdk/src/db_connection_impl.ts new file mode 100644 index 00000000000..d4ef11f66c0 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/db_connection_impl.ts @@ -0,0 +1,904 @@ +import { ConnectionId } from './connection_id'; +import { + AlgebraicType, + ProductType, + ProductTypeElement, + SumType, + SumTypeVariant, + type ComparablePrimitive, +} from './algebraic_type.ts'; +import { + AlgebraicValue, + parseValue, + ProductValue, + type ReducerArgsAdapter, + type ValueAdapter, +} from './algebraic_value.ts'; +import BinaryReader from './binary_reader.ts'; +import BinaryWriter from './binary_writer.ts'; +import { BsatnRowList } from './client_api/bsatn_row_list_type.ts'; +import { ClientMessage } from './client_api/client_message_type.ts'; +import { DatabaseUpdate } from './client_api/database_update_type.ts'; +import { QueryUpdate } from './client_api/query_update_type.ts'; +import { ServerMessage } from './client_api/server_message_type.ts'; +import { TableUpdate as RawTableUpdate } from './client_api/table_update_type.ts'; +import type * as clientApi from './client_api/index.ts'; +import { ClientCache } from './client_cache.ts'; +import { DbConnectionBuilder } from './db_connection_builder.ts'; +import { type DbContext } from './db_context.ts'; +import type { Event } from './event.ts'; +import { + type ErrorContextInterface, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from './event_context.ts'; +import { EventEmitter } from './event_emitter.ts'; +import { decompress } from './decompress.ts'; +import type { Identity } from './identity.ts'; +import type { + IdentityTokenMessage, + Message, + SubscribeAppliedMessage, + UnsubscribeAppliedMessage, +} from './message_types.ts'; +import type { ReducerEvent } from './reducer_event.ts'; +import type RemoteModule from './spacetime_module.ts'; +import { + TableCache, + type Operation, + type PendingCallback, + type TableUpdate as CacheTableUpdate, +} from './table_cache.ts'; +import { deepEqual, toPascalCase } from './utils.ts'; +import { WebsocketDecompressAdapter } from './websocket_decompress_adapter.ts'; +import type { WebsocketTestAdapter } from './websocket_test_adapter.ts'; +import { + SubscriptionBuilderImpl, + SubscriptionHandleImpl, + SubscriptionManager, + type SubscribeEvent, +} from './subscription_builder_impl.ts'; +import { stdbLogger } from './logger.ts'; +import { type ReducerRuntimeTypeInfo } from './spacetime_module.ts'; +import { fromByteArray } from 'base64-js'; + +export { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + DbConnectionBuilder, + deepEqual, + ProductType, + ProductTypeElement, + ProductValue, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + type Event, + type ReducerArgsAdapter, + type ValueAdapter, +}; + +export type { + DbContext, + EventContextInterface, + ReducerEventContextInterface, + SubscriptionEventContextInterface, + ErrorContextInterface, + ReducerEvent, +}; + +export type ConnectionEvent = 'connect' | 'disconnect' | 'connectError'; +export type CallReducerFlags = 'FullUpdate' | 'NoSuccessNotify'; + +type ReducerEventCallback = ( + ctx: ReducerEventContextInterface, + ...args: ReducerArgs +) => void; +type SubscriptionEventCallback = ( + ctx: SubscriptionEventContextInterface +) => void; +type ErrorCallback = (ctx: ErrorContextInterface) => void; + +function callReducerFlagsToNumber(flags: CallReducerFlags): number { + switch (flags) { + case 'FullUpdate': + return 0; + case 'NoSuccessNotify': + return 1; + } +} + +type DbConnectionConfig = { + uri: URL; + nameOrAddress: string; + identity?: Identity; + token?: string; + emitter: EventEmitter; + remoteModule: RemoteModule; + createWSFn: typeof WebsocketDecompressAdapter.createWebSocketFn; + compression: 'gzip' | 'none'; + lightMode: boolean; +}; + +export class DbConnectionImpl< + DBView = any, + Reducers = any, + SetReducerFlags = any, +> implements DbContext +{ + /** + * Whether or not the connection is active. + */ + isActive = false; + + /** + * This connection's public identity. + */ + identity?: Identity = undefined; + + /** + * This connection's private authentication token. + */ + token?: string = undefined; + + /** + * The accessor field to access the tables in the database and associated + * callback functions. + */ + db: DBView; + + /** + * The accessor field to access the reducers in the database and associated + * callback functions. + */ + reducers: Reducers; + + /** + * The accessor field to access functions related to setting flags on + * reducers regarding how the server should handle the reducer call and + * the events that it sends back to the client. + */ + setReducerFlags: SetReducerFlags; + + /** + * The `ConnectionId` of the connection to to the database. + */ + connectionId: ConnectionId = ConnectionId.random(); + + // These fields are meant to be strictly private. + #queryId = 0; + #emitter: EventEmitter; + #reducerEmitter: EventEmitter = + new EventEmitter(); + #onApplied?: SubscriptionEventCallback; + #remoteModule: RemoteModule; + #messageQueue = Promise.resolve(); + #subscriptionManager = new SubscriptionManager(); + + // These fields are not part of the public API, but in a pinch you + // could use JavaScript to access them by bypassing TypeScript's + // private fields. + // We use them in testing. + private clientCache: ClientCache; + private ws?: WebsocketDecompressAdapter | WebsocketTestAdapter; + private wsPromise: Promise< + WebsocketDecompressAdapter | WebsocketTestAdapter | undefined + >; + + constructor({ + uri, + nameOrAddress, + identity, + token, + emitter, + remoteModule, + createWSFn, + compression, + lightMode, + }: DbConnectionConfig) { + stdbLogger('info', 'Connecting to SpacetimeDB WS...'); + + let url = new URL(uri); + if (!/^wss?:/.test(uri.protocol)) { + url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'; + } + + this.identity = identity; + this.token = token; + + this.#remoteModule = remoteModule; + this.#emitter = emitter; + + let connectionId = this.connectionId.toHexString(); + url.searchParams.set('connection_id', connectionId); + + this.clientCache = new ClientCache(); + this.db = this.#remoteModule.dbViewConstructor(this); + this.setReducerFlags = this.#remoteModule.setReducerFlagsConstructor(); + this.reducers = this.#remoteModule.reducersConstructor( + this, + this.setReducerFlags + ); + + this.wsPromise = createWSFn({ + url, + nameOrAddress, + wsProtocol: 'v1.bsatn.spacetimedb', + authToken: token, + compression: compression, + lightMode: lightMode, + }) + .then(v => { + this.ws = v; + + this.ws.onclose = () => { + this.#emitter.emit('disconnect', this); + }; + this.ws.onerror = (e: ErrorEvent) => { + this.#emitter.emit('connectError', this, e); + }; + this.ws.onopen = this.#handleOnOpen.bind(this); + this.ws.onmessage = this.#handleOnMessage.bind(this); + return v; + }) + .catch(e => { + stdbLogger('error', 'Error connecting to SpacetimeDB WS'); + this.#emitter.emit('connectError', this, e); + + return undefined; + }); + } + + #getNextQueryId = () => { + const queryId = this.#queryId; + this.#queryId += 1; + return queryId; + }; + + // NOTE: This is very important!!! This is the actual function that + // gets called when you call `connection.subscriptionBuilder()`. + // The `subscriptionBuilder` function which is generated, just shadows + // this function in the type system, but not the actual implementation! + // Do not remove this function, or shoot yourself in the foot please. + // It's not clear what would be a better way to do this at this exact + // moment. + subscriptionBuilder = (): SubscriptionBuilderImpl => { + return new SubscriptionBuilderImpl(this); + }; + + registerSubscription( + handle: SubscriptionHandleImpl, + handleEmitter: EventEmitter, + querySql: string[] + ): number { + const queryId = this.#getNextQueryId(); + this.#subscriptionManager.subscriptions.set(queryId, { + handle, + emitter: handleEmitter, + }); + this.#sendMessage( + ClientMessage.SubscribeMulti({ + queryStrings: querySql, + queryId: { id: queryId }, + // The TypeScript SDK doesn't currently track `request_id`s, + // so always use 0. + requestId: 0, + }) + ); + return queryId; + } + + unregisterSubscription(queryId: number): void { + this.#sendMessage( + ClientMessage.UnsubscribeMulti({ + queryId: { id: queryId }, + // The TypeScript SDK doesn't currently track `request_id`s, + // so always use 0. + requestId: 0, + }) + ); + } + + // This function is async because we decompress the message async + async #processParsedMessage( + message: ServerMessage + ): Promise { + const parseRowList = ( + type: 'insert' | 'delete', + tableName: string, + rowList: BsatnRowList + ): Operation[] => { + const buffer = rowList.rowsData; + const reader = new BinaryReader(buffer); + const rows: Operation[] = []; + const rowType = this.#remoteModule.tables[tableName]!.rowType; + const primaryKeyInfo = + this.#remoteModule.tables[tableName]!.primaryKeyInfo; + while (reader.offset < buffer.length + buffer.byteOffset) { + const initialOffset = reader.offset; + const row = rowType.deserialize(reader); + let rowId: ComparablePrimitive | undefined = undefined; + if (primaryKeyInfo !== undefined) { + rowId = primaryKeyInfo.colType.intoMapKey( + row[primaryKeyInfo.colName] + ); + } else { + // Get a view of the bytes for this row. + const rowBytes = buffer.subarray( + initialOffset - buffer.byteOffset, + reader.offset - buffer.byteOffset + ); + // Convert it to a base64 string, so we can use it as a map key. + const asBase64 = fromByteArray(rowBytes); + rowId = asBase64; + } + + rows.push({ + type, + rowId, + row, + }); + } + return rows; + }; + + const parseTableUpdate = async ( + rawTableUpdate: RawTableUpdate + ): Promise => { + const tableName = rawTableUpdate.tableName; + let operations: Operation[] = []; + for (const update of rawTableUpdate.updates) { + let decompressed: QueryUpdate; + if (update.tag === 'Gzip') { + const decompressedBuffer = await decompress(update.value, 'gzip'); + decompressed = QueryUpdate.deserialize( + new BinaryReader(decompressedBuffer) + ); + } else if (update.tag === 'Brotli') { + throw new Error( + 'Brotli compression not supported. Please use gzip or none compression in withCompression method on DbConnection.' + ); + } else { + decompressed = update.value; + } + operations = operations.concat( + parseRowList('insert', tableName, decompressed.inserts) + ); + operations = operations.concat( + parseRowList('delete', tableName, decompressed.deletes) + ); + } + return { + tableName, + operations, + }; + }; + + const parseDatabaseUpdate = async ( + dbUpdate: DatabaseUpdate + ): Promise => { + const tableUpdates: CacheTableUpdate[] = []; + for (const rawTableUpdate of dbUpdate.tables) { + tableUpdates.push(await parseTableUpdate(rawTableUpdate)); + } + return tableUpdates; + }; + + switch (message.tag) { + case 'InitialSubscription': { + const dbUpdate = message.value.databaseUpdate; + const tableUpdates = await parseDatabaseUpdate(dbUpdate); + const subscriptionUpdate: Message = { + tag: 'InitialSubscription', + tableUpdates, + }; + return subscriptionUpdate; + } + + case 'TransactionUpdateLight': { + const dbUpdate = message.value.update; + const tableUpdates = await parseDatabaseUpdate(dbUpdate); + const subscriptionUpdate: Message = { + tag: 'TransactionUpdateLight', + tableUpdates, + }; + return subscriptionUpdate; + } + + case 'TransactionUpdate': { + const txUpdate = message.value; + const identity = txUpdate.callerIdentity; + const connectionId = ConnectionId.nullIfZero( + txUpdate.callerConnectionId + ); + const reducerName: string = txUpdate.reducerCall.reducerName; + const args = txUpdate.reducerCall.args; + const energyQuantaUsed = txUpdate.energyQuantaUsed; + + let tableUpdates: CacheTableUpdate[]; + let errMessage = ''; + switch (txUpdate.status.tag) { + case 'Committed': + tableUpdates = await parseDatabaseUpdate(txUpdate.status.value); + break; + case 'Failed': + tableUpdates = []; + errMessage = txUpdate.status.value; + break; + case 'OutOfEnergy': + tableUpdates = []; + break; + } + + // TODO: Can `reducerName` be ''? + // See: https://github.com/clockworklabs/SpacetimeDB/blob/a2a1b5d9b2e0ebaaf753d074db056d319952d442/crates/core/src/client/message_handlers.rs#L155 + if (reducerName === '') { + let errorMessage = errMessage; + console.error(`Received an error from the database: ${errorMessage}`); + return; + } + + let reducerInfo: + | { + reducerName: string; + args: Uint8Array; + } + | undefined; + if (reducerName !== '') { + reducerInfo = { + reducerName, + args, + }; + } + + const transactionUpdate: Message = { + tag: 'TransactionUpdate', + tableUpdates, + identity, + connectionId, + reducerInfo, + status: txUpdate.status, + energyConsumed: energyQuantaUsed.quanta, + message: errMessage, + timestamp: txUpdate.timestamp, + }; + return transactionUpdate; + } + + case 'IdentityToken': { + const identityTokenMessage: IdentityTokenMessage = { + tag: 'IdentityToken', + identity: message.value.identity, + token: message.value.token, + connectionId: message.value.connectionId, + }; + return identityTokenMessage; + } + + case 'OneOffQueryResponse': { + throw new Error( + `TypeScript SDK never sends one-off queries, but got OneOffQueryResponse ${message}` + ); + } + + case 'SubscribeMultiApplied': { + const parsedTableUpdates = await parseDatabaseUpdate( + message.value.update + ); + const subscribeAppliedMessage: SubscribeAppliedMessage = { + tag: 'SubscribeApplied', + queryId: message.value.queryId.id, + tableUpdates: parsedTableUpdates, + }; + return subscribeAppliedMessage; + } + + case 'UnsubscribeMultiApplied': { + const parsedTableUpdates = await parseDatabaseUpdate( + message.value.update + ); + const unsubscribeAppliedMessage: UnsubscribeAppliedMessage = { + tag: 'UnsubscribeApplied', + queryId: message.value.queryId.id, + tableUpdates: parsedTableUpdates, + }; + return unsubscribeAppliedMessage; + } + + case 'SubscriptionError': { + return { + tag: 'SubscriptionError', + queryId: message.value.queryId, + error: message.value.error, + }; + } + } + } + + #sendMessage(message: ClientMessage): void { + this.wsPromise.then(wsResolved => { + if (wsResolved) { + const writer = new BinaryWriter(1024); + ClientMessage.serialize(writer, message); + const encoded = writer.getBuffer(); + wsResolved.send(encoded); + } + }); + } + + /** + * Handles WebSocket onOpen event. + */ + #handleOnOpen(): void { + this.isActive = true; + } + + #applyTableUpdates( + tableUpdates: CacheTableUpdate[], + eventContext: EventContextInterface + ): PendingCallback[] { + let pendingCallbacks: PendingCallback[] = []; + for (let tableUpdate of tableUpdates) { + // Get table information for the table being updated + const tableName = tableUpdate.tableName; + const tableTypeInfo = this.#remoteModule.tables[tableName]!; + const table = this.clientCache.getOrCreateTable(tableTypeInfo); + const newCallbacks = table.applyOperations( + tableUpdate.operations, + eventContext + ); + for (const callback of newCallbacks) { + pendingCallbacks.push(callback); + } + } + return pendingCallbacks; + } + + async #processMessage(data: Uint8Array): Promise { + const serverMessage = parseValue(ServerMessage, data); + const message = await this.#processParsedMessage(serverMessage); + if (!message) { + return; + } + switch (message.tag) { + case 'InitialSubscription': { + let event: Event = { tag: 'SubscribeApplied' }; + + const eventContext = this.#remoteModule.eventContextConstructor( + this, + event + ); + // Remove the event from the subscription event context + // It is not a field in the type narrowed SubscriptionEventContext + const { event: _, ...subscriptionEventContext } = eventContext; + const callbacks = this.#applyTableUpdates( + message.tableUpdates, + eventContext + ); + + if (this.#emitter) { + this.#onApplied?.(subscriptionEventContext); + } + for (const callback of callbacks) { + callback.cb(); + } + break; + } + case 'TransactionUpdateLight': { + let event: Event = { tag: 'UnknownTransaction' }; + const eventContext = this.#remoteModule.eventContextConstructor( + this, + event + ); + const callbacks = this.#applyTableUpdates( + message.tableUpdates, + eventContext + ); + for (const callback of callbacks) { + callback.cb(); + } + break; + } + case 'TransactionUpdate': { + let reducerInfo = message.reducerInfo; + let unknownTransaction = false; + let reducerArgs: any | undefined; + let reducerTypeInfo: ReducerRuntimeTypeInfo | undefined; + if (!reducerInfo) { + unknownTransaction = true; + } else { + reducerTypeInfo = + this.#remoteModule.reducers[reducerInfo.reducerName]; + try { + const reader = new BinaryReader(reducerInfo.args as Uint8Array); + reducerArgs = reducerTypeInfo.argsType.deserialize(reader); + } catch { + // This should only be printed in development, since it's + // possible for clients to receive new reducers that they don't + // know about. + console.debug('Failed to deserialize reducer arguments'); + unknownTransaction = true; + } + } + + if (unknownTransaction) { + const event: Event = { tag: 'UnknownTransaction' }; + const eventContext = this.#remoteModule.eventContextConstructor( + this, + event + ); + const callbacks = this.#applyTableUpdates( + message.tableUpdates, + eventContext + ); + + for (const callback of callbacks) { + callback.cb(); + } + return; + } + + // At this point, we know that `reducerInfo` is not null because + // we return if `unknownTransaction` is true. + reducerInfo = reducerInfo!; + reducerTypeInfo = reducerTypeInfo!; + + // Thus this must be a reducer event create it and emit it. + const reducerEvent = { + callerIdentity: message.identity, + status: message.status, + callerConnectionId: message.connectionId as ConnectionId, + timestamp: message.timestamp, + energyConsumed: message.energyConsumed, + reducer: { + name: reducerInfo.reducerName, + args: reducerArgs, + }, + }; + const event: Event = { + tag: 'Reducer', + value: reducerEvent, + }; + const eventContext = this.#remoteModule.eventContextConstructor( + this, + event + ); + const reducerEventContext = { + ...eventContext, + event: reducerEvent, + }; + + const callbacks = this.#applyTableUpdates( + message.tableUpdates, + eventContext + ); + + const argsArray: any[] = []; + reducerTypeInfo.argsType.product.elements.forEach((element, index) => { + argsArray.push(reducerArgs[element.name]); + }); + this.#reducerEmitter.emit( + reducerInfo.reducerName, + reducerEventContext, + ...argsArray + ); + for (const callback of callbacks) { + callback.cb(); + } + break; + } + case 'IdentityToken': { + this.identity = message.identity; + if (!this.token && message.token) { + this.token = message.token; + } + this.connectionId = message.connectionId; + this.#emitter.emit('connect', this, this.identity, this.token); + break; + } + case 'SubscribeApplied': { + const subscription = this.#subscriptionManager.subscriptions.get( + message.queryId + ); + if (subscription === undefined) { + stdbLogger( + 'error', + `Received SubscribeApplied for unknown queryId ${message.queryId}.` + ); + // If we don't know about the subscription, we won't apply the table updates. + break; + } + const event: Event = { tag: 'SubscribeApplied' }; + const eventContext = this.#remoteModule.eventContextConstructor( + this, + event + ); + const { event: _, ...subscriptionEventContext } = eventContext; + const callbacks = this.#applyTableUpdates( + message.tableUpdates, + eventContext + ); + subscription?.emitter.emit('applied', subscriptionEventContext); + for (const callback of callbacks) { + callback.cb(); + } + break; + } + case 'UnsubscribeApplied': { + const subscription = this.#subscriptionManager.subscriptions.get( + message.queryId + ); + if (subscription === undefined) { + stdbLogger( + 'error', + `Received UnsubscribeApplied for unknown queryId ${message.queryId}.` + ); + // If we don't know about the subscription, we won't apply the table updates. + break; + } + const event: Event = { tag: 'UnsubscribeApplied' }; + const eventContext = this.#remoteModule.eventContextConstructor( + this, + event + ); + const { event: _, ...subscriptionEventContext } = eventContext; + const callbacks = this.#applyTableUpdates( + message.tableUpdates, + eventContext + ); + subscription?.emitter.emit('end', subscriptionEventContext); + this.#subscriptionManager.subscriptions.delete(message.queryId); + for (const callback of callbacks) { + callback.cb(); + } + break; + } + case 'SubscriptionError': { + const error = Error(message.error); + const event: Event = { tag: 'Error', value: error }; + const eventContext = this.#remoteModule.eventContextConstructor( + this, + event + ); + const errorContext = { + ...eventContext, + event: error, + }; + if (message.queryId !== undefined) { + this.#subscriptionManager.subscriptions + .get(message.queryId) + ?.emitter.emit('error', errorContext, error); + this.#subscriptionManager.subscriptions.delete(message.queryId); + } else { + console.error('Received an error message without a queryId: ', error); + // TODO: This should actually kill the connection. + // A subscription error without a specific subscription means we aren't receiving + // updates for all of our subscriptions, so our cache is out of sync. + + // Send it to all of them: + this.#subscriptionManager.subscriptions.forEach(({ emitter }) => { + emitter.emit('error', errorContext, error); + }); + } + } + } + } + + /** + * Handles WebSocket onMessage event. + * @param wsMessage MessageEvent object. + */ + #handleOnMessage(wsMessage: { data: Uint8Array }): void { + // Utilize promise chaining to ensure that we process messages in order + // even though we are processing them asyncronously. This will not begin + // processing the next message until we await the processing of the + // current message. + this.#messageQueue = this.#messageQueue.then(() => { + return this.#processMessage(wsMessage.data); + }); + } + + /** + * Call a reducer on your SpacetimeDB module. + * + * @param reducerName The name of the reducer to call + * @param argsSerializer The arguments to pass to the reducer + */ + callReducer( + reducerName: string, + argsBuffer: Uint8Array, + flags: CallReducerFlags + ): void { + const message = ClientMessage.CallReducer({ + reducer: reducerName, + args: argsBuffer, + // The TypeScript SDK doesn't currently track `request_id`s, + // so always use 0. + requestId: 0, + flags: callReducerFlagsToNumber(flags), + }); + this.#sendMessage(message); + } + + /** + * Close the current connection. + * + * @example + * + * ```ts + * const connection = DbConnection.builder().build(); + * connection.disconnect() + * ``` + */ + disconnect(): void { + this.wsPromise.then(wsResolved => { + if (wsResolved) { + wsResolved.close(); + } + }); + } + + #on( + eventName: ConnectionEvent, + callback: (ctx: DbConnectionImpl, ...args: any[]) => void + ): void { + this.#emitter.on(eventName, callback); + } + + #off( + eventName: ConnectionEvent, + callback: (ctx: DbConnectionImpl, ...args: any[]) => void + ): void { + this.#emitter.off(eventName, callback); + } + + #onConnect(callback: (ctx: DbConnectionImpl, ...args: any[]) => void): void { + this.#emitter.on('connect', callback); + } + + #onDisconnect( + callback: (ctx: DbConnectionImpl, ...args: any[]) => void + ): void { + this.#emitter.on('disconnect', callback); + } + + #onConnectError( + callback: (ctx: DbConnectionImpl, ...args: any[]) => void + ): void { + this.#emitter.on('connectError', callback); + } + + #removeOnConnect( + callback: (ctx: DbConnectionImpl, ...args: any[]) => void + ): void { + this.#emitter.off('connect', callback); + } + + #removeOnDisconnect( + callback: (ctx: DbConnectionImpl, ...args: any[]) => void + ): void { + this.#emitter.off('disconnect', callback); + } + + #removeOnConnectError( + callback: (ctx: DbConnectionImpl, ...args: any[]) => void + ): void { + this.#emitter.off('connectError', callback); + } + + // Note: This is required to be public because it needs to be + // called from the `RemoteReducers` class. + onReducer(reducerName: string, callback: ReducerEventCallback): void { + this.#reducerEmitter.on(reducerName, callback); + } + + // Note: This is required to be public because it needs to be + // called from the `RemoteReducers` class. + offReducer(reducerName: string, callback: ReducerEventCallback): void { + this.#reducerEmitter.off(reducerName, callback); + } +} diff --git a/sdks/typescript/packages/sdk/src/db_context.ts b/sdks/typescript/packages/sdk/src/db_context.ts new file mode 100644 index 00000000000..533ce32e923 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/db_context.ts @@ -0,0 +1,35 @@ +import type { SubscriptionBuilderImpl } from './subscription_builder_impl'; + +/** + * Interface representing a database context. + * + * @template DBView - Type representing the database view. + * @template Reducers - Type representing the reducers. + * @template SetReducerFlags - Type representing the reducer flags collection. + */ +export interface DbContext< + DBView = any, + Reducers = any, + SetReducerFlags = any, +> { + db: DBView; + reducers: Reducers; + setReducerFlags: SetReducerFlags; + isActive: boolean; + + /** + * Creates a new subscription builder. + * + * @returns The subscription builder. + */ + subscriptionBuilder(): SubscriptionBuilderImpl< + DBView, + Reducers, + SetReducerFlags + >; + + /** + * Disconnects from the database. + */ + disconnect(): void; +} diff --git a/sdks/typescript/packages/sdk/src/decompress.ts b/sdks/typescript/packages/sdk/src/decompress.ts new file mode 100644 index 00000000000..cdc49a2241a --- /dev/null +++ b/sdks/typescript/packages/sdk/src/decompress.ts @@ -0,0 +1,52 @@ +export async function decompress( + buffer: Uint8Array, + // Leaving it here to expand to brotli when it lands in the browsers and NodeJS + type: 'gzip', + chunkSize: number = 128 * 1024 // 128KB +): Promise { + // Create a single ReadableStream to handle chunks + let offset = 0; + const readableStream = new ReadableStream({ + pull(controller) { + if (offset < buffer.length) { + // Slice a chunk of the buffer and enqueue it + const chunk = buffer.subarray( + offset, + Math.min(offset + chunkSize, buffer.length) + ); + controller.enqueue(chunk); + offset += chunkSize; + } else { + controller.close(); + } + }, + }); + + // Create a single DecompressionStream + const decompressionStream = new DecompressionStream(type); + + // Pipe the ReadableStream through the DecompressionStream + const decompressedStream = readableStream.pipeThrough(decompressionStream); + + // Collect the decompressed chunks efficiently + const reader = decompressedStream.getReader(); + const chunks: Uint8Array[] = []; + let totalLength = 0; + let result: any; + + while (!(result = await reader.read()).done) { + chunks.push(result.value); + totalLength += result.value.length; + } + + // Allocate a single Uint8Array for the decompressed data + const decompressedArray = new Uint8Array(totalLength); + let chunkOffset = 0; + + for (const chunk of chunks) { + decompressedArray.set(chunk, chunkOffset); + chunkOffset += chunk.length; + } + + return decompressedArray; +} diff --git a/sdks/typescript/packages/sdk/src/event.ts b/sdks/typescript/packages/sdk/src/event.ts new file mode 100644 index 00000000000..8be677e47ab --- /dev/null +++ b/sdks/typescript/packages/sdk/src/event.ts @@ -0,0 +1,8 @@ +import type { ReducerEvent, ReducerInfoType } from './reducer_event'; + +export type Event = + | { tag: 'Reducer'; value: ReducerEvent } + | { tag: 'SubscribeApplied' } + | { tag: 'UnsubscribeApplied' } + | { tag: 'Error'; value: Error } + | { tag: 'UnknownTransaction' }; diff --git a/sdks/typescript/packages/sdk/src/event_context.ts b/sdks/typescript/packages/sdk/src/event_context.ts new file mode 100644 index 00000000000..f4c67bc5685 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/event_context.ts @@ -0,0 +1,40 @@ +import type { DbContext } from './db_context'; +import type { Event } from './event.ts'; +import type { ReducerEvent, ReducerInfoType } from './reducer_event.ts'; + +export interface EventContextInterface< + DBView = any, + Reducers = any, + SetReducerFlags = any, + Reducer extends ReducerInfoType = never, +> extends DbContext { + /** Enum with variants for all possible events. */ + event: Event; +} + +export interface ReducerEventContextInterface< + DBView = any, + Reducers = any, + SetReducerFlags = any, + Reducer extends ReducerInfoType = never, +> extends DbContext { + /** Enum with variants for all possible events. */ + event: ReducerEvent; +} + +export interface SubscriptionEventContextInterface< + DBView = any, + Reducers = any, + SetReducerFlags = any, +> extends DbContext { + /** No event is provided **/ +} + +export interface ErrorContextInterface< + DBView = any, + Reducers = any, + SetReducerFlags = any, +> extends DbContext { + /** Enum with variants for all possible events. */ + event?: Error; +} diff --git a/sdks/typescript/packages/sdk/src/event_emitter.ts b/sdks/typescript/packages/sdk/src/event_emitter.ts new file mode 100644 index 00000000000..26c702a5e67 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/event_emitter.ts @@ -0,0 +1,31 @@ +export class EventEmitter { + #events: Map> = new Map(); + + on(event: Key, callback: Callback): void { + let callbacks = this.#events.get(event); + if (!callbacks) { + callbacks = new Set(); + this.#events.set(event, callbacks); + } + callbacks.add(callback); + } + + off(event: Key, callback: Callback): void { + let callbacks = this.#events.get(event); + if (!callbacks) { + return; + } + callbacks.delete(callback); + } + + emit(event: Key, ...args: any[]): void { + let callbacks = this.#events.get(event); + if (!callbacks) { + return; + } + + for (let callback of callbacks) { + callback(...args); + } + } +} diff --git a/sdks/typescript/packages/sdk/src/identity.ts b/sdks/typescript/packages/sdk/src/identity.ts new file mode 100644 index 00000000000..9ce25dccbdc --- /dev/null +++ b/sdks/typescript/packages/sdk/src/identity.ts @@ -0,0 +1,53 @@ +import BinaryReader from './binary_reader'; +import BinaryWriter from './binary_writer'; +import { hexStringToU256, u256ToHexString, u256ToUint8Array } from './utils'; + +/** + * A unique identifier for a user connected to a database. + */ +export class Identity { + data: bigint; + + get __identity__(): bigint { + return this.data; + } + + /** + * Creates a new `Identity`. + * + * `data` can be a hexadecimal string or a `bigint`. + */ + constructor(data: string | bigint) { + // we get a JSON with __identity__ when getting a token with a JSON API + // and an bigint when using BSATN + this.data = typeof data === 'string' ? hexStringToU256(data) : data; + } + + /** + * Compare two identities for equality. + */ + isEqual(other: Identity): boolean { + return this.toHexString() === other.toHexString(); + } + + /** + * Print the identity as a hexadecimal string. + */ + toHexString(): string { + return u256ToHexString(this.data); + } + + /** + * Convert the address to a Uint8Array. + */ + toUint8Array(): Uint8Array { + return u256ToUint8Array(this.data); + } + + /** + * Parse an Identity from a hexadecimal string. + */ + static fromString(str: string): Identity { + return new Identity(str); + } +} diff --git a/sdks/typescript/packages/sdk/src/index.ts b/sdks/typescript/packages/sdk/src/index.ts new file mode 100644 index 00000000000..7aa615aaebe --- /dev/null +++ b/sdks/typescript/packages/sdk/src/index.ts @@ -0,0 +1,10 @@ +// Should be at the top as other modules depend on it +export * from './db_connection_impl.ts'; + +export * from './connection_id.ts'; +export * from './schedule_at'; +export * from './client_cache.ts'; +export * from './identity.ts'; +export * from './message_types.ts'; +export * from './timestamp.ts'; +export * from './time_duration.ts'; diff --git a/sdks/typescript/packages/sdk/src/json_api.ts b/sdks/typescript/packages/sdk/src/json_api.ts new file mode 100644 index 00000000000..e48cff24f1d --- /dev/null +++ b/sdks/typescript/packages/sdk/src/json_api.ts @@ -0,0 +1,46 @@ +export interface Message { + IdentityToken?: IdentityToken | undefined; + SubscriptionUpdate?: SubscriptionUpdate | undefined; + TransactionUpdate?: TransactionUpdate | undefined; +} + +export interface IdentityToken { + identity: string; + token: string; + address: string; +} + +export interface SubscriptionUpdate { + table_updates: TableUpdate[]; +} + +export interface TableUpdate { + table_id: number; + table_name: string; + table_row_operations: TableRowOperation[]; +} + +export interface TableRowOperation { + op: 'insert' | 'delete'; + row: any[]; +} + +export interface TransactionUpdate { + event: Event; + subscription_update: SubscriptionUpdate; +} + +export interface Event { + timestamp: number; + status: 'committed' | 'failed' | 'out_of_energy'; + caller_identity: string; + caller_address: string; + function_call: FunctionCall; + energy_quanta_used: number; + message: string; +} + +export interface FunctionCall { + reducer: string; + args: string; +} diff --git a/sdks/typescript/packages/sdk/src/logger.ts b/sdks/typescript/packages/sdk/src/logger.ts new file mode 100644 index 00000000000..2b986862e90 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/logger.ts @@ -0,0 +1,36 @@ +type LogLevel = 'info' | 'warn' | 'error' | 'debug'; + +const LogLevelIdentifierIcon = { + component: '📦', + info: 'ℹ️', + warn: '⚠️', + error: '❌', + debug: '🐛', +}; + +const LogStyle = { + component: + 'color: #fff; background-color: #8D6FDD; padding: 2px 5px; border-radius: 3px;', + info: 'color: #fff; background-color: #007bff; padding: 2px 5px; border-radius: 3px;', + warn: 'color: #fff; background-color: #ffc107; padding: 2px 5px; border-radius: 3px;', + error: + 'color: #fff; background-color: #dc3545; padding: 2px 5px; border-radius: 3px;', + debug: + 'color: #fff; background-color: #28a745; padding: 2px 5px; border-radius: 3px;', +}; + +const LogTextStyle = { + component: 'color: #8D6FDD;', + info: 'color: #007bff;', + warn: 'color: #ffc107;', + error: 'color: #dc3545;', + debug: 'color: #28a745;', +}; + +export const stdbLogger = (level: LogLevel, message: any): void => { + console.log( + `%c${LogLevelIdentifierIcon[level]} ${level.toUpperCase()}%c ${message}`, + LogStyle[level], + LogTextStyle[level] + ); +}; diff --git a/sdks/typescript/packages/sdk/src/message_types.ts b/sdks/typescript/packages/sdk/src/message_types.ts new file mode 100644 index 00000000000..3ce1f9ca982 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/message_types.ts @@ -0,0 +1,64 @@ +import { ConnectionId } from './connection_id'; +import type { UpdateStatus } from './client_api/index.ts'; +import { Identity } from './identity.ts'; +import type { TableUpdate } from './table_cache.ts'; +import { Timestamp } from './timestamp.ts'; + +export type InitialSubscriptionMessage = { + tag: 'InitialSubscription'; + tableUpdates: TableUpdate[]; +}; + +export type TransactionUpdateMessage = { + tag: 'TransactionUpdate'; + tableUpdates: TableUpdate[]; + identity: Identity; + connectionId: ConnectionId | null; + reducerInfo?: { + reducerName: string; + args: Uint8Array; + }; + status: UpdateStatus; + message: string; + timestamp: Timestamp; + energyConsumed: bigint; +}; + +export type TransactionUpdateLightMessage = { + tag: 'TransactionUpdateLight'; + tableUpdates: TableUpdate[]; +}; + +export type IdentityTokenMessage = { + tag: 'IdentityToken'; + identity: Identity; + token: string; + connectionId: ConnectionId; +}; + +export type SubscribeAppliedMessage = { + tag: 'SubscribeApplied'; + queryId: number; + tableUpdates: TableUpdate[]; +}; + +export type UnsubscribeAppliedMessage = { + tag: 'UnsubscribeApplied'; + queryId: number; + tableUpdates: TableUpdate[]; +}; + +export type SubscriptionError = { + tag: 'SubscriptionError'; + queryId?: number; + error: string; +}; + +export type Message = + | InitialSubscriptionMessage + | TransactionUpdateMessage + | TransactionUpdateLightMessage + | IdentityTokenMessage + | SubscribeAppliedMessage + | UnsubscribeAppliedMessage + | SubscriptionError; diff --git a/sdks/typescript/packages/sdk/src/reducer_event.ts b/sdks/typescript/packages/sdk/src/reducer_event.ts new file mode 100644 index 00000000000..8711dbfb139 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/reducer_event.ts @@ -0,0 +1,45 @@ +import { ConnectionId } from './connection_id'; +import { Timestamp } from './timestamp.ts'; +import type { UpdateStatus } from './client_api/index.ts'; +import { Identity } from './identity.ts'; + +export type ReducerInfoType = { name: string; args?: any } | never; + +export type ReducerEvent = { + /** + * The time when the reducer started running. + * + * @internal This is a number and not Date, as JSON.stringify with date in it gives number, but JSON.parse of the same string does not give date. TO avoid + * confusion in typing we'll keep it a number + */ + timestamp: Timestamp; + + /** + * Whether the reducer committed, was aborted due to insufficient energy, or failed with an error message. + */ + status: UpdateStatus; + + /** + * The identity of the caller. + * TODO: Revise these to reflect the forthcoming Identity proposal. + */ + callerIdentity: Identity; + + /** + * The connection ID of the caller. + * + * May be `null`, e.g. for scheduled reducers. + */ + callerConnectionId?: ConnectionId; + + /** + * The amount of energy consumed by the reducer run, in eV. + * (Not literal eV, but our SpacetimeDB energy unit eV.) + * May be present or undefined at the implementor's discretion; + * future work may determine an interface for module developers + * to request this value be published or hidden. + */ + energyConsumed?: bigint; + + reducer: Reducer; +}; diff --git a/sdks/typescript/packages/sdk/src/schedule_at.ts b/sdks/typescript/packages/sdk/src/schedule_at.ts new file mode 100644 index 00000000000..e31dd692f5e --- /dev/null +++ b/sdks/typescript/packages/sdk/src/schedule_at.ts @@ -0,0 +1,45 @@ +import { AlgebraicType, SumTypeVariant } from './algebraic_type'; +import type { AlgebraicValue } from './algebraic_value'; + +export namespace ScheduleAt { + export function getAlgebraicType(): AlgebraicType { + return AlgebraicType.createSumType([ + new SumTypeVariant('Interval', AlgebraicType.createU64Type()), + new SumTypeVariant('Time', AlgebraicType.createU64Type()), + ]); + } + + export function serialize(value: ScheduleAt): object { + switch (value.tag) { + case 'Interval': + return { Interval: value.value }; + case 'Time': + return { Time: value.value }; + default: + throw 'unreachable'; + } + } + + export type Interval = { tag: 'Interval'; value: BigInt }; + export const Interval = (value: BigInt): Interval => ({ + tag: 'Interval', + value, + }); + export type Time = { tag: 'Time'; value: BigInt }; + export const Time = (value: BigInt): Time => ({ tag: 'Time', value }); + + export function fromValue(value: AlgebraicValue): ScheduleAt { + let sumValue = value.asSumValue(); + switch (sumValue.tag) { + case 0: + return { tag: 'Interval', value: sumValue.value.asBigInt() }; + case 1: + return { tag: 'Time', value: sumValue.value.asBigInt() }; + default: + throw 'unreachable'; + } + } +} + +export type ScheduleAt = ScheduleAt.Interval | ScheduleAt.Time; +export default ScheduleAt; diff --git a/sdks/typescript/packages/sdk/src/serializer.ts b/sdks/typescript/packages/sdk/src/serializer.ts new file mode 100644 index 00000000000..ca1130adbd7 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/serializer.ts @@ -0,0 +1,123 @@ +import { AlgebraicType } from './algebraic_type'; +import type { MapValue } from './algebraic_value'; +import BinaryWriter from './binary_writer'; + +export interface Serializer { + write(type: AlgebraicType, value: any): any; + args(): any; +} + +export class BinarySerializer { + #writer: BinaryWriter; + + constructor() { + this.#writer = new BinaryWriter(1024); + } + + args(): any { + return this.getBuffer(); + } + + getBuffer(): Uint8Array { + return this.#writer.getBuffer(); + } + + write(type: AlgebraicType, value: any): void { + switch (type.type) { + case AlgebraicType.Type.ProductType: { + for (const element of type.product.elements) { + this.write(element.algebraicType, value[element.name]); + } + break; + } + case AlgebraicType.Type.SumType: + const sum = type.sum; + if ( + sum.variants.length == 2 && + sum.variants[0].name === 'some' && + sum.variants[1].name === 'none' + ) { + if (value) { + this.writeByte(0); + this.write(sum.variants[0].algebraicType, value); + } else { + this.writeByte(1); + } + } else { + const index = type.sum.variants.findIndex(v => v.name === value.tag); + if (index < 0) { + throw `Can't serialize a sum type, couldn't find ${value.tag} tag`; + } + + this.writeByte(index); + this.write(sum.variants[index].algebraicType, value.value); + } + break; + case AlgebraicType.Type.ArrayType: + const elemTy = type.array; + const array = value as any[]; + this.#writer.writeU32(array.length); + for (const element of array) { + this.write(elemTy, element); + } + break; + case AlgebraicType.Type.MapType: + const mapTy = type.map; + const map = value as MapValue; + this.#writer.writeU32(map.size); + for (const entry of map) { + this.write(mapTy.keyType, entry[0]); + this.write(mapTy.valueType, entry[1]); + } + break; + case AlgebraicType.Type.String: + this.#writer.writeString(value); + break; + case AlgebraicType.Type.Bool: + this.#writer.writeBool(value); + break; + case AlgebraicType.Type.I8: + this.#writer.writeI8(value); + break; + case AlgebraicType.Type.U8: + this.#writer.writeU8(value); + break; + case AlgebraicType.Type.I16: + this.#writer.writeI16(value); + break; + case AlgebraicType.Type.U16: + this.#writer.writeU16(value); + break; + case AlgebraicType.Type.I32: + this.#writer.writeI32(value); + break; + case AlgebraicType.Type.U32: + this.#writer.writeU32(value); + break; + case AlgebraicType.Type.I64: + this.#writer.writeI64(value); + break; + case AlgebraicType.Type.U64: + this.#writer.writeU64(value); + break; + case AlgebraicType.Type.I128: + this.#writer.writeI128(value); + break; + case AlgebraicType.Type.U128: + this.#writer.writeU128(value); + break; + case AlgebraicType.Type.F32: + this.#writer.writeF32(value); + break; + case AlgebraicType.Type.F64: + this.#writer.writeF64(value); + break; + default: + break; + } + } + + writeByte(byte: number): void { + this.#writer.writeU8(byte); + } +} diff --git a/sdks/typescript/packages/sdk/src/spacetime_module.ts b/sdks/typescript/packages/sdk/src/spacetime_module.ts new file mode 100644 index 00000000000..1ccefe2deb5 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/spacetime_module.ts @@ -0,0 +1,33 @@ +import type { AlgebraicType } from './algebraic_type'; +import type { DbConnectionImpl } from './db_connection_impl'; + +export interface TableRuntimeTypeInfo { + tableName: string; + rowType: AlgebraicType; + primaryKeyInfo?: PrimaryKeyInfo; +} + +export interface PrimaryKeyInfo { + colName: string; + colType: AlgebraicType; +} + +export interface ReducerRuntimeTypeInfo { + reducerName: string; + argsType: AlgebraicType; +} + +export default interface RemoteModule { + tables: { [name: string]: TableRuntimeTypeInfo }; + reducers: { [name: string]: ReducerRuntimeTypeInfo }; + eventContextConstructor: (imp: DbConnectionImpl, event: any) => any; + dbViewConstructor: (connection: DbConnectionImpl) => any; + reducersConstructor: ( + connection: DbConnectionImpl, + setReducerFlags: any + ) => any; + setReducerFlagsConstructor: () => any; + versionInfo?: { + cliVersion: string; + }; +} diff --git a/sdks/typescript/packages/sdk/src/subscription_builder_impl.ts b/sdks/typescript/packages/sdk/src/subscription_builder_impl.ts new file mode 100644 index 00000000000..5fffd581237 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/subscription_builder_impl.ts @@ -0,0 +1,276 @@ +import type { DbConnectionImpl } from './db_connection_impl'; +import type { + ErrorContextInterface, + SubscriptionEventContextInterface, +} from './event_context'; +import { EventEmitter } from './event_emitter'; + +export class SubscriptionBuilderImpl< + DBView = any, + Reducers = any, + SetReducerFlags = any, +> { + #onApplied?: ( + ctx: SubscriptionEventContextInterface + ) => void = undefined; + #onError?: ( + ctx: ErrorContextInterface + ) => void = undefined; + constructor( + private db: DbConnectionImpl + ) {} + + /** + * Registers `callback` to run when this query is successfully added to our subscribed set, + * I.e. when its `SubscriptionApplied` message is received. + * + * The database state exposed via the `&EventContext` argument + * includes all the rows added to the client cache as a result of the new subscription. + * + * The event in the `&EventContext` argument is `Event::SubscribeApplied`. + * + * Multiple `on_applied` callbacks for the same query may coexist. + * No mechanism for un-registering `on_applied` callbacks is exposed. + * + * @param cb - Callback to run when the subscription is applied. + * @returns The current `SubscriptionBuilder` instance. + */ + onApplied( + cb: ( + ctx: SubscriptionEventContextInterface + ) => void + ): SubscriptionBuilderImpl { + this.#onApplied = cb; + return this; + } + + /** + * Registers `callback` to run when this query either: + * - Fails to be added to our subscribed set. + * - Is unexpectedly removed from our subscribed set. + * + * If the subscription had previously started and has been unexpectedly removed, + * the database state exposed via the `&EventContext` argument contains no rows + * from any subscriptions removed within the same error event. + * As proposed, it must therefore contain no rows. + * + * The event in the `&EventContext` argument is `Event::SubscribeError`, + * containing a dynamic error object with a human-readable description of the error + * for diagnostic purposes. + * + * Multiple `on_error` callbacks for the same query may coexist. + * No mechanism for un-registering `on_error` callbacks is exposed. + * + * @param cb - Callback to run when there is an error in subscription. + * @returns The current `SubscriptionBuilder` instance. + */ + onError( + cb: (ctx: ErrorContextInterface) => void + ): SubscriptionBuilderImpl { + this.#onError = cb; + return this; + } + + /** + * Subscribe to a single query. The results of the query will be merged into the client + * cache and deduplicated on the client. + * + * @param query_sql A `SQL` query to subscribe to. + * + * @example + * + * ```ts + * const subscription = connection.subscriptionBuilder().onApplied(() => { + * console.log("SDK client cache initialized."); + * }).subscribe("SELECT * FROM User"); + * + * subscription.unsubscribe(); + * ``` + */ + subscribe( + query_sql: string | string[] + ): SubscriptionHandleImpl { + const queries = Array.isArray(query_sql) ? query_sql : [query_sql]; + if (queries.length === 0) { + throw new Error('Subscriptions must have at least one query'); + } + return new SubscriptionHandleImpl( + this.db, + queries, + this.#onApplied, + this.#onError + ); + } + + /** + * Subscribes to all rows from all tables. + * + * This method is intended as a convenience + * for applications where client-side memory use and network bandwidth are not concerns. + * Applications where these resources are a constraint + * should register more precise queries via `subscribe` + * in order to replicate only the subset of data which the client needs to function. + * + * This method should not be combined with `subscribe` on the same `DbConnection`. + * A connection may either `subscribe` to particular queries, + * or `subscribeToAllTables`, but not both. + * Attempting to call `subscribe` + * on a `DbConnection` that has previously used `subscribeToAllTables`, + * or vice versa, may misbehave in any number of ways, + * including dropping subscriptions, corrupting the client cache, or throwing errors. + */ + subscribeToAllTables(): void { + this.subscribe('SELECT * FROM *'); + } +} + +export type SubscribeEvent = 'applied' | 'error' | 'end'; + +export class SubscriptionManager { + subscriptions: Map< + number, + { handle: SubscriptionHandleImpl; emitter: EventEmitter } + > = new Map(); +} + +export class SubscriptionHandleImpl< + DBView = any, + Reducers = any, + SetReducerFlags = any, +> { + #queryId: number; + #unsubscribeCalled: boolean = false; + #endedState: boolean = false; + #activeState: boolean = false; + #emitter: EventEmitter void> = + new EventEmitter(); + + constructor( + private db: DbConnectionImpl, + querySql: string[], + onApplied?: ( + ctx: SubscriptionEventContextInterface + ) => void, + onError?: ( + ctx: ErrorContextInterface, + error: Error + ) => void + ) { + this.#emitter.on( + 'applied', + ( + ctx: SubscriptionEventContextInterface< + DBView, + Reducers, + SetReducerFlags + > + ) => { + this.#activeState = true; + if (onApplied) { + onApplied(ctx); + } + } + ); + this.#emitter.on( + 'error', + ( + ctx: ErrorContextInterface, + error: Error + ) => { + this.#activeState = false; + this.#endedState = true; + if (onError) { + onError(ctx, error); + } + } + ); + this.#queryId = this.db.registerSubscription(this, this.#emitter, querySql); + } + + /** + * Consumes self and issues an `Unsubscribe` message, + * removing this query from the client's set of subscribed queries. + * It is only valid to call this method if `is_active()` is `true`. + */ + unsubscribe(): void { + if (this.#unsubscribeCalled) { + throw new Error('Unsubscribe has already been called'); + } + this.#unsubscribeCalled = true; + this.db.unregisterSubscription(this.#queryId); + this.#emitter.on( + 'end', + ( + _ctx: SubscriptionEventContextInterface< + DBView, + Reducers, + SetReducerFlags + > + ) => { + this.#endedState = true; + this.#activeState = false; + } + ); + } + + /** + * Unsubscribes and also registers a callback to run upon success. + * I.e. when an `UnsubscribeApplied` message is received. + * + * If `Unsubscribe` returns an error, + * or if the `on_error` callback(s) are invoked before this subscription would end normally, + * the `on_end` callback is not invoked. + * + * @param onEnd - Callback to run upon successful unsubscribe. + */ + unsubscribeThen( + onEnd: ( + ctx: SubscriptionEventContextInterface + ) => void + ): void { + if (this.#endedState) { + throw new Error('Subscription has already ended'); + } + if (this.#unsubscribeCalled) { + throw new Error('Unsubscribe has already been called'); + } + this.#unsubscribeCalled = true; + this.db.unregisterSubscription(this.#queryId); + this.#emitter.on( + 'end', + ( + ctx: SubscriptionEventContextInterface< + DBView, + Reducers, + SetReducerFlags + > + ) => { + this.#endedState = true; + this.#activeState = false; + onEnd(ctx); + } + ); + } + + /** + * True if this `SubscriptionHandle` has ended, + * either due to an error or a call to `unsubscribe`. + * + * This is initially false, and becomes true when either the `on_end` or `on_error` callback is invoked. + * A subscription which has not yet been applied is not active, but is also not ended. + */ + isEnded(): boolean { + return this.#endedState; + } + + /** + * True if this `SubscriptionHandle` is active, meaning it has been successfully applied + * and has not since ended, either due to an error or a complete `unsubscribe` request-response pair. + * + * This corresponds exactly to the interval bounded at the start by the `on_applied` callback + * and at the end by either the `on_end` or `on_error` callback. + */ + isActive(): boolean { + return this.#activeState; + } +} diff --git a/sdks/typescript/packages/sdk/src/table_cache.ts b/sdks/typescript/packages/sdk/src/table_cache.ts new file mode 100644 index 00000000000..8763c2189f6 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/table_cache.ts @@ -0,0 +1,326 @@ +import { EventEmitter } from './event_emitter.ts'; +import type { TableRuntimeTypeInfo } from './spacetime_module.ts'; + +import { + BinaryWriter, + type EventContextInterface, +} from './db_connection_impl.ts'; +import { stdbLogger } from './logger.ts'; +import type { ComparablePrimitive } from './algebraic_type.ts'; + +export type Operation = { + type: 'insert' | 'delete'; + // For tables with a primary key, this is the primary key value, as a primitive or string. + // Otherwise, it is an encoding of the full row. + rowId: ComparablePrimitive; + // TODO: Refine this type to at least reflect that it is a product. + row: any; +}; + +export type TableUpdate = { + tableName: string; + operations: Operation[]; +}; + +export type PendingCallback = { + type: 'insert' | 'delete' | 'update'; + table: string; + cb: () => void; +}; +/** + * Builder to generate calls to query a `table` in the database + */ +export class TableCache { + private rows: Map; + private tableTypeInfo: TableRuntimeTypeInfo; + private emitter: EventEmitter<'insert' | 'delete' | 'update'>; + + /** + * @param name the table name + * @param primaryKeyCol column index designated as `#[primarykey]` + * @param primaryKey column name designated as `#[primarykey]` + * @param entityClass the entityClass + */ + constructor(tableTypeInfo: TableRuntimeTypeInfo) { + this.tableTypeInfo = tableTypeInfo; + this.rows = new Map(); + this.emitter = new EventEmitter(); + } + + /** + * @returns number of rows in the table + */ + count(): number { + return this.rows.size; + } + + /** + * @returns The values of the rows in the table + */ + iter(): any[] { + return Array.from(this.rows.values()).map(([row]) => row); + } + + applyOperations = ( + operations: Operation[], + ctx: EventContextInterface + ): PendingCallback[] => { + const pendingCallbacks: PendingCallback[] = []; + if (this.tableTypeInfo.primaryKeyInfo !== undefined) { + const insertMap = new Map(); + const deleteMap = new Map(); + for (const op of operations) { + if (op.type === 'insert') { + const [_, prevCount] = insertMap.get(op.rowId) || [op, 0]; + insertMap.set(op.rowId, [op, prevCount + 1]); + } else { + const [_, prevCount] = deleteMap.get(op.rowId) || [op, 0]; + deleteMap.set(op.rowId, [op, prevCount + 1]); + } + } + for (const [primaryKey, [insertOp, refCount]] of insertMap) { + const deleteEntry = deleteMap.get(primaryKey); + if (deleteEntry) { + const [_, deleteCount] = deleteEntry; + // In most cases the refCountDelta will be either 0 or refCount, but if + // an update moves a row in or out of the result set of different queries, then + // other deltas are possible. + const refCountDelta = refCount - deleteCount; + const maybeCb = this.update( + ctx, + primaryKey, + insertOp.row, + refCountDelta + ); + if (maybeCb) { + pendingCallbacks.push(maybeCb); + } + deleteMap.delete(primaryKey); + } else { + const maybeCb = this.insert(ctx, insertOp, refCount); + if (maybeCb) { + pendingCallbacks.push(maybeCb); + } + } + } + for (const [deleteOp, refCount] of deleteMap.values()) { + const maybeCb = this.delete(ctx, deleteOp, refCount); + if (maybeCb) { + pendingCallbacks.push(maybeCb); + } + } + } else { + for (const op of operations) { + if (op.type === 'insert') { + const maybeCb = this.insert(ctx, op); + if (maybeCb) { + pendingCallbacks.push(maybeCb); + } + } else { + const maybeCb = this.delete(ctx, op); + if (maybeCb) { + pendingCallbacks.push(maybeCb); + } + } + } + } + return pendingCallbacks; + }; + + update = ( + ctx: EventContextInterface, + rowId: ComparablePrimitive, + newRow: RowType, + refCountDelta: number = 0 + ): PendingCallback | undefined => { + const existingEntry = this.rows.get(rowId); + if (!existingEntry) { + // TODO: this should throw an error and kill the connection. + stdbLogger( + 'error', + `Updating a row that was not present in the cache. Table: ${this.tableTypeInfo.tableName}, RowId: ${rowId}` + ); + return undefined; + } + const [oldRow, previousCount] = existingEntry; + const refCount = Math.max(1, previousCount + refCountDelta); + if (previousCount + refCountDelta <= 0) { + stdbLogger( + 'error', + `Negative reference count for in table ${this.tableTypeInfo.tableName} row ${rowId} (${previousCount} + ${refCountDelta})` + ); + return undefined; + } + this.rows.set(rowId, [newRow, refCount]); + // This indicates something is wrong, so we could arguably crash here. + if (previousCount === 0) { + stdbLogger( + 'error', + `Updating a row id in table ${this.tableTypeInfo.tableName} which was not present in the cache (rowId: ${rowId})` + ); + return { + type: 'insert', + table: this.tableTypeInfo.tableName, + cb: () => { + this.emitter.emit('insert', ctx, newRow); + }, + }; + } + return { + type: 'update', + table: this.tableTypeInfo.tableName, + cb: () => { + this.emitter.emit('update', ctx, oldRow, newRow); + }, + }; + }; + + insert = ( + ctx: EventContextInterface, + operation: Operation, + count: number = 1 + ): PendingCallback | undefined => { + const [_, previousCount] = this.rows.get(operation.rowId) || [ + operation.row, + 0, + ]; + this.rows.set(operation.rowId, [operation.row, previousCount + count]); + if (previousCount === 0) { + return { + type: 'insert', + table: this.tableTypeInfo.tableName, + cb: () => { + this.emitter.emit('insert', ctx, operation.row); + }, + }; + } + // It's possible to get a duplicate insert because rows can be returned from multiple queries. + return undefined; + }; + + delete = ( + ctx: EventContextInterface, + operation: Operation, + count: number = 1 + ): PendingCallback | undefined => { + const [_, previousCount] = this.rows.get(operation.rowId) || [ + operation.row, + 0, + ]; + // This should never happen. + if (previousCount === 0) { + stdbLogger('warn', 'Deleting a row that was not present in the cache'); + return undefined; + } + // If this was the last reference, we are actually deleting the row. + if (previousCount <= count) { + // TODO: Log a warning/error if previousCount is less than count. + this.rows.delete(operation.rowId); + return { + type: 'delete', + table: this.tableTypeInfo.tableName, + cb: () => { + this.emitter.emit('delete', ctx, operation.row); + }, + }; + } + this.rows.set(operation.rowId, [operation.row, previousCount - count]); + return undefined; + }; + + /** + * Register a callback for when a row is newly inserted into the database. + * + * ```ts + * User.onInsert((user, reducerEvent) => { + * if (reducerEvent) { + * console.log("New user on reducer", reducerEvent, user); + * } else { + * console.log("New user received during subscription update on insert", user); + * } + * }); + * ``` + * + * @param cb Callback to be called when a new row is inserted + */ + onInsert = ( + cb: (ctx: EventContext, row: RowType) => void + ): void => { + this.emitter.on('insert', cb); + }; + + /** + * Register a callback for when a row is deleted from the database. + * + * ```ts + * User.onDelete((user, reducerEvent) => { + * if (reducerEvent) { + * console.log("Deleted user on reducer", reducerEvent, user); + * } else { + * console.log("Deleted user received during subscription update on update", user); + * } + * }); + * ``` + * + * @param cb Callback to be called when a new row is inserted + */ + onDelete = ( + cb: (ctx: EventContext, row: RowType) => void + ): void => { + this.emitter.on('delete', cb); + }; + + /** + * Register a callback for when a row is updated into the database. + * + * ```ts + * User.onInsert((user, reducerEvent) => { + * if (reducerEvent) { + * console.log("Updated user on reducer", reducerEvent, user); + * } else { + * console.log("Updated user received during subscription update on delete", user); + * } + * }); + * ``` + * + * @param cb Callback to be called when a new row is inserted + */ + onUpdate = ( + cb: (ctx: EventContext, oldRow: RowType, row: RowType) => void + ): void => { + this.emitter.on('update', cb); + }; + + /** + * Remove a callback for when a row is newly inserted into the database. + * + * @param cb Callback to be removed + */ + removeOnInsert = ( + cb: (ctx: EventContext, row: RowType) => void + ): void => { + this.emitter.off('insert', cb); + }; + + /** + * Remove a callback for when a row is deleted from the database. + * + * @param cb Callback to be removed + */ + removeOnDelete = ( + cb: (ctx: EventContext, row: RowType) => void + ): void => { + this.emitter.off('delete', cb); + }; + + /** + * Remove a callback for when a row is updated into the database. + * + * @param cb Callback to be removed + */ + removeOnUpdate = ( + cb: (ctx: EventContext, oldRow: RowType, row: RowType) => void + ): void => { + this.emitter.off('update', cb); + }; +} diff --git a/sdks/typescript/packages/sdk/src/time_duration.ts b/sdks/typescript/packages/sdk/src/time_duration.ts new file mode 100644 index 00000000000..6179ccd3140 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/time_duration.ts @@ -0,0 +1,24 @@ +/** + * A difference between two points in time, represented as a number of microseconds. + */ +export class TimeDuration { + __time_duration_micros__: bigint; + + private static MICROS_PER_MILLIS: bigint = 1000n; + + get micros(): bigint { + return this.__time_duration_micros__; + } + + get millis(): number { + return Number(this.micros / TimeDuration.MICROS_PER_MILLIS); + } + + constructor(micros: bigint) { + this.__time_duration_micros__ = micros; + } + + static fromMillis(millis: number): TimeDuration { + return new TimeDuration(BigInt(millis) * TimeDuration.MICROS_PER_MILLIS); + } +} diff --git a/sdks/typescript/packages/sdk/src/timestamp.ts b/sdks/typescript/packages/sdk/src/timestamp.ts new file mode 100644 index 00000000000..6a18fd131b7 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/timestamp.ts @@ -0,0 +1,57 @@ +/** + * A point in time, represented as a number of microseconds since the Unix epoch. + */ +export class Timestamp { + __timestamp_micros_since_unix_epoch__: bigint; + + private static MICROS_PER_MILLIS: bigint = 1000n; + + get microsSinceUnixEpoch(): bigint { + return this.__timestamp_micros_since_unix_epoch__; + } + + constructor(micros: bigint) { + this.__timestamp_micros_since_unix_epoch__ = micros; + } + + /** + * The Unix epoch, the midnight at the beginning of January 1, 1970, UTC. + */ + static UNIX_EPOCH: Timestamp = new Timestamp(0n); + + /** + * Get a `Timestamp` representing the execution environment's belief of the current moment in time. + */ + static now(): Timestamp { + return Timestamp.fromDate(new Date()); + } + + /** + * Get a `Timestamp` representing the same point in time as `date`. + */ + static fromDate(date: Date): Timestamp { + const millis = date.getTime(); + const micros = BigInt(millis) * Timestamp.MICROS_PER_MILLIS; + return new Timestamp(micros); + } + + /** + * Get a `Date` representing approximately the same point in time as `this`. + * + * This method truncates to millisecond precision, + * and throws `RangeError` if the `Timestamp` is outside the range representable as a `Date`. + */ + toDate(): Date { + const micros = this.__timestamp_micros_since_unix_epoch__; + const millis = micros / Timestamp.MICROS_PER_MILLIS; + if ( + millis > BigInt(Number.MAX_SAFE_INTEGER) || + millis < BigInt(Number.MIN_SAFE_INTEGER) + ) { + throw new RangeError( + "Timestamp is outside of the representable range of JS's Date" + ); + } + return new Date(Number(millis)); + } +} diff --git a/sdks/typescript/packages/sdk/src/utils.ts b/sdks/typescript/packages/sdk/src/utils.ts new file mode 100644 index 00000000000..4ab3b4609a7 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/utils.ts @@ -0,0 +1,101 @@ +import BinaryReader from './binary_reader'; +import BinaryWriter from './binary_writer'; + +export function toPascalCase(s: string): string { + const str = s.replace(/([-_][a-z])/gi, $1 => { + return $1.toUpperCase().replace('-', '').replace('_', ''); + }); + + return str.charAt(0).toUpperCase() + str.slice(1); +} + +export function deepEqual(obj1: any, obj2: any): boolean { + // If both are strictly equal (covers primitives and reference equality), return true + if (obj1 === obj2) return true; + + // If either is a primitive type or one is null, return false since we already checked for strict equality + if ( + typeof obj1 !== 'object' || + obj1 === null || + typeof obj2 !== 'object' || + obj2 === null + ) { + return false; + } + + // Get keys of both objects + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + + // If number of keys is different, return false + if (keys1.length !== keys2.length) return false; + + // Check all keys and compare values recursively + for (let key of keys1) { + if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) { + return false; + } + } + + return true; +} + +export function uint8ArrayToHexString(array: Uint8Array): string { + return Array.prototype.map + .call(array.reverse(), x => ('00' + x.toString(16)).slice(-2)) + .join(''); +} + +export function uint8ArrayToU128(array: Uint8Array): bigint { + if (array.length != 16) { + throw new Error(`Uint8Array is not 16 bytes long: ${array}`); + } + return new BinaryReader(array).readU128(); +} + +export function uint8ArrayToU256(array: Uint8Array): bigint { + if (array.length != 32) { + throw new Error(`Uint8Array is not 32 bytes long: [${array}]`); + } + return new BinaryReader(array).readU256(); +} + +export function hexStringToUint8Array(str: string): Uint8Array { + if (str.startsWith('0x')) { + str = str.slice(2); + } + let matches = str.match(/.{1,2}/g) || []; + let data = Uint8Array.from(matches.map((byte: string) => parseInt(byte, 16))); + if (data.length != 32) { + return new Uint8Array(0); + } + return data.reverse(); +} + +export function hexStringToU128(str: string): bigint { + return uint8ArrayToU128(hexStringToUint8Array(str)); +} + +export function hexStringToU256(str: string): bigint { + return uint8ArrayToU256(hexStringToUint8Array(str)); +} + +export function u128ToUint8Array(data: bigint): Uint8Array { + let writer = new BinaryWriter(16); + writer.writeU128(data); + return writer.getBuffer(); +} + +export function u128ToHexString(data: bigint): string { + return uint8ArrayToHexString(u128ToUint8Array(data)); +} + +export function u256ToUint8Array(data: bigint): Uint8Array { + let writer = new BinaryWriter(32); + writer.writeU256(data); + return writer.getBuffer(); +} + +export function u256ToHexString(data: bigint): string { + return uint8ArrayToHexString(u256ToUint8Array(data)); +} diff --git a/sdks/typescript/packages/sdk/src/version.ts b/sdks/typescript/packages/sdk/src/version.ts new file mode 100644 index 00000000000..9221a121994 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/version.ts @@ -0,0 +1,135 @@ +export type PrereleaseId = string | number; + +export type PreRelease = PrereleaseId[]; + +// Compare pre-release identifiers according to the semver spec (https://semver.org/#spec-item-11). +function comparePreReleases(a: PreRelease, b: PreRelease): number { + const len = Math.min(a.length, b.length); + for (let i = 0; i < len; i++) { + const aPart = a[i]; + const bPart = b[i]; + if (aPart === bPart) continue; + if (typeof aPart === 'number' && typeof bPart === 'number') { + return aPart - bPart; + } + if (typeof aPart === 'string' && typeof bPart === 'string') { + return aPart.localeCompare(bPart); + } + // According to item 11.4.3, numeric identifiers always have lower precedence than non-numeric identifiers. + // So if `a` is a string, it has higher precedence than `b`. + return typeof aPart === 'string' ? 1 : -1; + } + // See rule 11.4.4 in the semver spec. + return a.length - b.length; +} + +// We don't use these, and they don't matter for version ordering, so I'm not going to parse it to spec. +export type BuildInfo = string; + +// This is exported for tests. +export class SemanticVersion { + major: number; + minor: number; + patch: number; + preRelease: PreRelease | null; + buildInfo: BuildInfo | null; + + constructor( + major: number, + minor: number, + patch: number, + preRelease: PreRelease | null = null, + buildInfo: BuildInfo | null = null + ) { + this.major = major; + this.minor = minor; + this.patch = patch; + this.preRelease = preRelease; + this.buildInfo = buildInfo; + } + + toString(): string { + let versionString = `${this.major}.${this.minor}.${this.patch}`; + if (this.preRelease) { + versionString += `-${this.preRelease.join('.')}`; + } + if (this.buildInfo) { + versionString += `+${this.buildInfo}`; + } + return versionString; + } + + compare(other: SemanticVersion): number { + if (this.major !== other.major) { + return this.major - other.major; + } + if (this.minor !== other.minor) { + return this.minor - other.minor; + } + if (this.patch !== other.patch) { + return this.patch - other.patch; + } + if (this.preRelease && other.preRelease) { + return comparePreReleases(this.preRelease, other.preRelease); + } + if (this.preRelease) { + return -1; // The version without a pre-release is greater. + } + if (other.preRelease) { + return -1; // Since we don't have a pre-release, this version is greater. + } + return 0; // versions are equal + } + + clone(): SemanticVersion { + return new SemanticVersion( + this.major, + this.minor, + this.patch, + this.preRelease ? [...this.preRelease] : null, + this.buildInfo + ); + } + + static parseVersionString(version: string): SemanticVersion { + const regex = + /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([\da-zA-Z-]+(?:\.[\da-zA-Z-]+)*))?(?:\+([\da-zA-Z-]+(?:\.[\da-zA-Z-]+)*))?$/; + const match = version.match(regex); + if (!match) { + throw new Error(`Invalid version string: ${version}`); + } + + const major = parseInt(match[1], 10); + const minor = parseInt(match[2], 10); + const patch = parseInt(match[3], 10); + const preRelease = match[4] + ? match[4].split('.').map(id => (isNaN(Number(id)) ? id : Number(id))) + : null; + const buildInfo = match[5] || null; + + return new SemanticVersion(major, minor, patch, preRelease, buildInfo); + } +} + +// The SDK depends on some module information that was not generated before this version. +export const _MINIMUM_CLI_VERSION: SemanticVersion = new SemanticVersion( + 1, + 2, + 0 +); + +export function ensureMinimumVersionOrThrow(versionString?: string): void { + if (versionString === undefined) { + throw new Error(versionErrorMessage(versionString)); + } + const version = SemanticVersion.parseVersionString(versionString); + if (version.compare(_MINIMUM_CLI_VERSION) < 0) { + throw new Error(versionErrorMessage(versionString)); + } +} + +function versionErrorMessage(incompatibleVersion?: string): string { + const badVersion = + incompatibleVersion === undefined ? 'unknown' : incompatibleVersion; + return `Module code was generated with an incompatible version of the spacetimedb cli (${incompatibleVersion}). Update the cli version to at least ${_MINIMUM_CLI_VERSION.toString()} and regenerate the bindings. You can upgrade to the latest cli version by running: spacetime version upgrade`; +} diff --git a/sdks/typescript/packages/sdk/src/websocket_decompress_adapter.ts b/sdks/typescript/packages/sdk/src/websocket_decompress_adapter.ts new file mode 100644 index 00000000000..fbd1d0faf84 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/websocket_decompress_adapter.ts @@ -0,0 +1,128 @@ +import { decompress } from './decompress'; + +export class WebsocketDecompressAdapter { + onclose?: (...ev: any[]) => void; + onopen?: (...ev: any[]) => void; + onmessage?: (msg: { data: Uint8Array }) => void; + onerror?: (msg: ErrorEvent) => void; + + #ws: WebSocket; + + async #handleOnMessage(msg: MessageEvent) { + const buffer = new Uint8Array(msg.data); + let decompressed: Uint8Array; + + if (buffer[0] === 0) { + decompressed = buffer.slice(1); + } else if (buffer[0] === 1) { + throw new Error( + 'Brotli Compression not supported. Please use gzip or none compression in withCompression method on DbConnection.' + ); + } else if (buffer[0] === 2) { + decompressed = await decompress(buffer.slice(1), 'gzip'); + } else { + throw new Error( + 'Unexpected Compression Algorithm. Please use `gzip` or `none`' + ); + } + + this.onmessage?.({ data: decompressed }); + } + + #handleOnOpen(msg: any) { + this.onopen?.(msg); + } + + #handleOnError(msg: any) { + this.onerror?.(msg); + } + + send(msg: any): void { + this.#ws.send(msg); + } + + close(): void { + this.#ws.close(); + } + + constructor(ws: WebSocket) { + this.onmessage = undefined; + this.onopen = undefined; + this.onmessage = undefined; + this.onerror = undefined; + + ws.onmessage = this.#handleOnMessage.bind(this); + ws.onerror = this.#handleOnError.bind(this); + ws.onclose = this.#handleOnError.bind(this); + ws.onopen = this.#handleOnOpen.bind(this); + + ws.binaryType = 'arraybuffer'; + + this.#ws = ws; + } + + static async createWebSocketFn({ + url, + nameOrAddress, + wsProtocol, + authToken, + compression, + lightMode, + }: { + url: URL; + wsProtocol: string; + nameOrAddress: string; + authToken?: string; + compression: 'gzip' | 'none'; + lightMode: boolean; + }): Promise { + const headers = new Headers(); + + let WS: typeof WebSocket; + + // @ts-ignore + if (import.meta.env.BROWSER === 'false') { + WS = + 'WebSocket' in globalThis + ? WebSocket + : ((await import('undici')).WebSocket as unknown as typeof WebSocket); + } else { + WS = WebSocket; + } + + // We swap our original token to a shorter-lived token + // to avoid sending the original via query params. + let temporaryAuthToken: string | undefined = undefined; + if (authToken) { + headers.set('Authorization', `Bearer ${authToken}`); + const tokenUrl = new URL('v1/identity/websocket-token', url); + tokenUrl.protocol = url.protocol === 'wss:' ? 'https:' : 'http:'; + + const response = await fetch(tokenUrl, { method: 'POST', headers }); + if (response.ok) { + const { token } = await response.json(); + temporaryAuthToken = token; + } else { + return Promise.reject( + new Error(`Failed to verify token: ${response.statusText}`) + ); + } + } + + const databaseUrl = new URL(`v1/database/${nameOrAddress}/subscribe`, url); + if (temporaryAuthToken) { + databaseUrl.searchParams.set('token', temporaryAuthToken); + } + databaseUrl.searchParams.set( + 'compression', + compression === 'gzip' ? 'Gzip' : 'None' + ); + if (lightMode) { + databaseUrl.searchParams.set('light', 'true'); + } + + const ws = new WS(databaseUrl.toString(), wsProtocol); + + return new WebsocketDecompressAdapter(ws); + } +} diff --git a/sdks/typescript/packages/sdk/src/websocket_test_adapter.ts b/sdks/typescript/packages/sdk/src/websocket_test_adapter.ts new file mode 100644 index 00000000000..fe151c89d91 --- /dev/null +++ b/sdks/typescript/packages/sdk/src/websocket_test_adapter.ts @@ -0,0 +1,52 @@ +import BinaryWriter from './binary_writer.ts'; +import { ServerMessage } from './client_api/index.ts'; + +class WebsocketTestAdapter { + onclose: any; + onopen!: Function; + onmessage: any; + onerror: any; + + messageQueue: any[]; + closed: boolean; + + constructor() { + this.messageQueue = []; + this.closed = false; + } + + send(message: any): void { + this.messageQueue.push(message); + } + + close(): void { + this.closed = true; + } + + acceptConnection(): void { + this.onopen(); + } + + sendToClient(message: ServerMessage): void { + const writer = new BinaryWriter(1024); + ServerMessage.getTypeScriptAlgebraicType().serialize(writer, message); + const rawBytes = writer.getBuffer(); + // The brotli library's `compress` is somehow broken: it returns `null` for some inputs. + // See https://github.com/foliojs/brotli.js/issues/36, which is closed but not actually fixed. + // So we send the uncompressed data here, and in `spacetimedb.ts`, + // if compression fails, we treat the raw message as having been uncompressed all along. + // const data = compress(rawBytes); + this.onmessage({ data: rawBytes }); + } + + async createWebSocketFn( + _url: string, + _protocol: string, + _params: any + ): Promise { + return this; + } +} + +export type { WebsocketTestAdapter }; +export default WebsocketTestAdapter; diff --git a/sdks/typescript/packages/sdk/tests/algebraic_value.test.ts b/sdks/typescript/packages/sdk/tests/algebraic_value.test.ts new file mode 100644 index 00000000000..12e778223ed --- /dev/null +++ b/sdks/typescript/packages/sdk/tests/algebraic_value.test.ts @@ -0,0 +1,170 @@ +import { describe, expect, test } from 'vitest'; +import { AlgebraicType } from '../src/algebraic_type'; +import { + AlgebraicValue, + BinaryAdapter, + ProductValue, + SumValue, +} from '../src/algebraic_value'; +import BinaryReader from '../src/binary_reader'; +import BinaryWriter from '../src/binary_writer'; + +describe('AlgebraicValue', () => { + test('when created with a ProductValue it assigns the product property', () => { + let value = new ProductValue([]); + let av = new AlgebraicValue(value); + expect(av.asProductValue()).toBe(value); + }); + + test('when created with a SumValue it assigns the sum property', () => { + let value = new SumValue(1, new AlgebraicValue(1)); + let av = new AlgebraicValue(value); + expect(av.asSumValue()).toBe(value); + }); + + test('when created with a AlgebraicValue(string) it can be requested as a string', () => { + let av = new AlgebraicValue('foo'); + + expect(av.asString()).toBe('foo'); + }); + + test('options handle falsy strings', () => { + let stringOptionType = AlgebraicType.createOptionType( + AlgebraicType.createStringType() + ); + let writer = new BinaryWriter(1024); + stringOptionType.serialize(writer, ''); + let parsed = stringOptionType.deserialize( + new BinaryReader(writer.getBuffer()) + ); + // Make sure we don't turn this into undefined. + expect(parsed).toEqual(''); + + writer = new BinaryWriter(1024); + stringOptionType.serialize(writer, null); + parsed = stringOptionType.deserialize(new BinaryReader(writer.getBuffer())); + expect(parsed).toEqual(undefined); + + writer = new BinaryWriter(1024); + stringOptionType.serialize(writer, undefined); + parsed = stringOptionType.deserialize(new BinaryReader(writer.getBuffer())); + expect(parsed).toEqual(undefined); + }); + + test('options handle falsy numbers', () => { + let stringOptionType = AlgebraicType.createOptionType( + AlgebraicType.createU32Type() + ); + let writer = new BinaryWriter(1024); + stringOptionType.serialize(writer, 0); + let parsed = stringOptionType.deserialize( + new BinaryReader(writer.getBuffer()) + ); + // Make sure we don't turn this into undefined. + expect(parsed).toEqual(0); + + writer = new BinaryWriter(1024); + stringOptionType.serialize(writer, null); + parsed = stringOptionType.deserialize(new BinaryReader(writer.getBuffer())); + expect(parsed).toEqual(undefined); + + writer = new BinaryWriter(1024); + stringOptionType.serialize(writer, undefined); + parsed = stringOptionType.deserialize(new BinaryReader(writer.getBuffer())); + expect(parsed).toEqual(undefined); + }); + + test('when created with a AlgebraicValue(AlgebraicValue[]) it can be requested as an array', () => { + let array: AlgebraicValue[] = [new AlgebraicValue(1)]; + let av = new AlgebraicValue(array); + expect(av.asArray()).toBe(array); + }); + + describe('deserialize with a binary adapter', () => { + test('should correctly deserialize array with U8 type', () => { + const input = new Uint8Array([2, 0, 0, 0, 10, 20]); + const reader = new BinaryReader(input); + const adapter: BinaryAdapter = new BinaryAdapter(reader); + const type = AlgebraicType.createBytesType(); + + const result = AlgebraicValue.deserialize(type, adapter); + + expect(result.asBytes()).toEqual(new Uint8Array([10, 20])); + }); + + test('should correctly deserialize array with U128 type', () => { + // byte array of length 0002 + // prettier-ignore + const input = new Uint8Array([ + 3, 0, 0, 0, // 4 bytes for length + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 bytes for u128 + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 16 bytes for max u128 + 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 bytes for u128 + ]); + const reader = new BinaryReader(input); + const adapter: BinaryAdapter = new BinaryAdapter(reader); + const type = AlgebraicType.createArrayType( + AlgebraicType.createU128Type() + ); + + const result = AlgebraicValue.deserialize(type, adapter); + + const u128_max = BigInt(2) ** BigInt(128) - BigInt(1); + expect(result.asArray().map(e => e.asBigInt())).toEqual([ + BigInt(1), + u128_max, + BigInt(10), + ]); + }); + + test('should correctly deserialize an U128 type', () => { + // byte array of length 0002 + // prettier-ignore + const input = new Uint8Array([ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 16 bytes for max u128 + ]); + const reader = new BinaryReader(input); + const adapter: BinaryAdapter = new BinaryAdapter(reader); + const result = AlgebraicValue.deserialize( + AlgebraicType.createU128Type(), + adapter + ); + + const u128_max = BigInt(2) ** BigInt(128) - BigInt(1); + expect(result.asBigInt()).toEqual(u128_max); + }); + + test('should correctly deserialize a boolean type', () => { + // byte array of length 0002 + const input = new Uint8Array([1]); + const reader = new BinaryReader(input); + const adapter: BinaryAdapter = new BinaryAdapter(reader); + const result = AlgebraicValue.deserialize( + AlgebraicType.createBoolType(), + adapter + ); + + expect(result.asBoolean()).toEqual(true); + }); + + test('should correctly deserialize a string type', () => { + // byte array of length 0002 + const text = 'zażółć gęślą jaźń'; + const encoder = new TextEncoder(); + const textBytes = encoder.encode(text); + + const input = new Uint8Array(textBytes.length + 4); + input.set(new Uint8Array([textBytes.length, 0, 0, 0])); + input.set(textBytes, 4); + + const reader = new BinaryReader(input); + const adapter: BinaryAdapter = new BinaryAdapter(reader); + const result = AlgebraicValue.deserialize( + AlgebraicType.createStringType(), + adapter + ); + + expect(result.asString()).toEqual('zażółć gęślą jaźń'); + }); + }); +}); diff --git a/sdks/typescript/packages/sdk/tests/binary_read_write.test.ts b/sdks/typescript/packages/sdk/tests/binary_read_write.test.ts new file mode 100644 index 00000000000..993f6675a9d --- /dev/null +++ b/sdks/typescript/packages/sdk/tests/binary_read_write.test.ts @@ -0,0 +1,111 @@ +import { describe, expect, test } from 'vitest'; +import BinaryReader from '../src/binary_reader'; +import BinaryWriter from '../src/binary_writer'; + +/* +// Generated by the following Rust code: + +#[cfg(test)] +mod tests { + use rand::{thread_rng, Rng}; + use spacetimedb_sats::{bsatn, i256, u256}; + + #[test] + fn make_some_numbers() { + let mut rng = thread_rng(); + let v_u8: u8 = rng.gen(); + let v_u16: u16 = rng.gen(); + let v_u32: u32 = rng.gen(); + let v_u64: u64 = rng.gen(); + let v_u128: u128 = rng.gen(); + let v_u256: u256 = u256::from_words(rng.gen(), rng.gen()); + let v_i8: i8 = rng.gen(); + let v_i16: i16 = rng.gen(); + let v_i32: i32 = rng.gen(); + let v_i64: i64 = rng.gen(); + let v_i128: i128 = rng.gen(); + let v_i256: i256 = i256::from_words(rng.gen(), rng.gen()); + + println!("['I8', {}, {:?}],", v_i8, bsatn::to_vec(&v_i8).unwrap()); + println!("['I16', {}, {:?}],", v_i16, bsatn::to_vec(&v_i16).unwrap()); + println!("['I32', {}, {:?}],", v_i32, bsatn::to_vec(&v_i32).unwrap()); + println!("['I64', BigInt('{}'), {:?}],", v_i64, bsatn::to_vec(&v_i64).unwrap()); + println!("['I128', BigInt('{}'), {:?}],", v_i128, bsatn::to_vec(&v_i128).unwrap()); + println!("['I256', BigInt('{}'), {:?}],", v_i256, bsatn::to_vec(&v_i256).unwrap()); + + println!("['U8', {}, {:?}],", v_u8, bsatn::to_vec(&v_u8).unwrap()); + println!("['U16', {}, {:?}],", v_u16, bsatn::to_vec(&v_u16).unwrap()); + println!("['U32', {}, {:?}],", v_u32, bsatn::to_vec(&v_u32).unwrap()); + println!("['U64', BigInt('{}'), {:?}],", v_u64, bsatn::to_vec(&v_u64).unwrap()); + println!("['U128', BigInt('{}'), {:?}],", v_u128, bsatn::to_vec(&v_u128).unwrap()); + println!("['U256', BigInt('{}'), {:?}],", v_u256, bsatn::to_vec(&v_u256).unwrap()); + panic!(); + } +} +*/ + +let testCases: Array<[string, BigInt | number, Array]> = [ + ['I8', 48, [48]], + ['I16', 2910, [94, 11]], + ['I32', -799760706, [190, 158, 84, 208]], + [ + 'I64', + BigInt('-1541553498090056195'), + [253, 213, 20, 208, 66, 77, 155, 234], + ], + [ + 'I128', + BigInt('12547586996680216771838914786222604020'), + [244, 254, 202, 102, 17, 36, 114, 210, 182, 88, 120, 98, 205, 147, 112, 9], + ], + [ + 'I256', + BigInt( + '35334490670013506332541201493144667192747188790291257662501378603950330458369' + ), + [ + 1, 177, 117, 147, 65, 153, 110, 71, 110, 80, 45, 231, 208, 112, 149, 150, + 251, 157, 51, 25, 129, 124, 13, 154, 238, 225, 7, 63, 237, 156, 30, 78, + ], + ], + ['U8', 63, [63]], + ['U16', 14776, [184, 57]], + ['U32', 2260346643, [19, 39, 186, 134]], + [ + 'U64', + BigInt('6355943419584016569'), + [185, 112, 201, 104, 221, 216, 52, 88], + ], + [ + 'U128', + BigInt('190443100270131819986139062814080853012'), + [20, 100, 201, 134, 99, 82, 196, 32, 34, 79, 25, 142, 199, 1, 70, 143], + ], + [ + 'U256', + BigInt( + '58716185326733447174109779681509939791568291171619953995835894271369692835957' + ), + [ + 117, 240, 99, 239, 213, 99, 55, 201, 2, 145, 4, 24, 0, 173, 62, 27, 124, + 53, 44, 244, 71, 1, 156, 30, 111, 187, 149, 150, 229, 46, 208, 129, + ], + ], +]; + +describe('BinaryReader/Writer', () => { + test('correctly reads/writes little endian values', () => { + for (let [name, int, buf] of testCases) { + let arr = new Uint8Array(buf); + let reader = new BinaryReader(arr); + + let read = reader['read' + name](); + expect(read).toEqual(int); + + let writer = new BinaryWriter(0); + writer['write' + name](int); + + expect(writer.getBuffer()).toEqual(arr); + } + }); +}); diff --git a/sdks/typescript/packages/sdk/tests/db_connection.test.ts b/sdks/typescript/packages/sdk/tests/db_connection.test.ts new file mode 100644 index 00000000000..8339256c9a6 --- /dev/null +++ b/sdks/typescript/packages/sdk/tests/db_connection.test.ts @@ -0,0 +1,681 @@ +import { + CreatePlayer, + DbConnection, + Player, + Point, + User, +} from '@clockworklabs/test-app/src/module_bindings'; +import { beforeEach, describe, expect, test } from 'vitest'; +import { ConnectionId } from '../src/connection_id'; +import { Timestamp } from '../src/timestamp'; +import { TimeDuration } from '../src/time_duration'; +import { AlgebraicType } from '../src/algebraic_type'; +import { parseValue } from '../src/algebraic_value'; +import BinaryWriter from '../src/binary_writer'; +import * as ws from '../src/client_api'; +import { ReducerEvent } from '../src/db_connection_impl'; +import { Identity } from '../src/identity'; +import WebsocketTestAdapter from '../src/websocket_test_adapter'; + +const anIdentity = Identity.fromString( + '0000000000000000000000000000000000000000000000000000000000000069' +); +const bobIdentity = Identity.fromString( + '0000000000000000000000000000000000000000000000000000000000000b0b' +); +const sallyIdentity = Identity.fromString( + '000000000000000000000000000000000000000000000000000000000006a111' +); + +class Deferred { + #isResolved: boolean = false; + #isRejected: boolean = false; + #resolve: (value: T | PromiseLike) => void; + #reject: (reason?: any) => void; + promise: Promise; + + constructor() { + this.promise = new Promise((resolve, reject) => { + this.#resolve = resolve; + this.#reject = reject; + }); + } + + // Getter for isResolved + get isResolved(): boolean { + return this.#isResolved; + } + + // Getter for isRejected + get isRejected(): boolean { + return this.#isRejected; + } + + // Resolve method + resolve(value: T): void { + if (!this.#isResolved && !this.#isRejected) { + this.#isResolved = true; + this.#resolve(value); + } + } + + // Reject method + reject(reason?: any): void { + if (!this.#isResolved && !this.#isRejected) { + this.#isRejected = true; + this.#reject(reason); + } + } +} + +beforeEach(() => {}); + +function encodePlayer(value: Player): Uint8Array { + const writer = new BinaryWriter(1024); + Player.serialize(writer, value); + return writer.getBuffer(); +} + +function encodeUser(value: User): Uint8Array { + const writer = new BinaryWriter(1024); + User.serialize(writer, value); + return writer.getBuffer(); +} + +function encodeCreatePlayerArgs(name: string, location: Point): Uint8Array { + const writer = new BinaryWriter(1024); + AlgebraicType.createStringType().serialize(writer, name); + Point.serialize(writer, location); + return writer.getBuffer(); +} + +describe('DbConnection', () => { + test('call onConnectError callback after websocket connection failed to be established', async () => { + const onConnectErrorPromise = new Deferred(); + + let errorCalled = false; + let connectCalled = false; + const client = DbConnection.builder() + .withUri('ws://127.0.0.1:1234') + .withModuleName('db') + .withWSFn(() => { + return Promise.reject(new Error('Failed to connect')); + }) + .onConnect(() => { + connectCalled = true; + }) + .onConnectError(() => { + errorCalled = true; + onConnectErrorPromise.resolve(); + }) + .build(); + + await client['wsPromise']; + await onConnectErrorPromise.promise; + expect(errorCalled).toBeTruthy(); + expect(connectCalled).toBeFalsy(); + }); + + test('call onConnect callback after getting an identity', async () => { + const onConnectPromise = new Deferred(); + + const wsAdapter = new WebsocketTestAdapter(); + let called = false; + const client = DbConnection.builder() + .withUri('ws://127.0.0.1:1234') + .withModuleName('db') + .withWSFn(wsAdapter.createWebSocketFn.bind(wsAdapter)) + .onConnect(() => { + called = true; + onConnectPromise.resolve(); + }) + .build(); + + await client['wsPromise']; + wsAdapter.acceptConnection(); + + const tokenMessage = ws.ServerMessage.IdentityToken({ + identity: anIdentity, + token: 'a-token', + connectionId: ConnectionId.random(), + }); + wsAdapter.sendToClient(tokenMessage); + + await onConnectPromise.promise; + + expect(called).toBeTruthy(); + }); + + test('it calls onInsert callback when a record is added with a subscription update and then with a transaction update', async () => { + const wsAdapter = new WebsocketTestAdapter(); + let called = false; + const client = DbConnection.builder() + .withUri('ws://127.0.0.1:1234') + .withModuleName('db') + .withWSFn(wsAdapter.createWebSocketFn.bind(wsAdapter)) + .onConnect(() => { + called = true; + }) + .build(); + + await Promise.race([ + client['wsPromise'], + new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout')), 1000) + ), + ]); + wsAdapter.acceptConnection(); + + const tokenMessage = ws.ServerMessage.IdentityToken({ + identity: anIdentity, + token: 'a-token', + connectionId: ConnectionId.random(), + }); + wsAdapter.sendToClient(tokenMessage); + + const inserts: { + reducerEvent: + | ReducerEvent<{ + name: 'CreatePlayer'; + args: CreatePlayer; + }> + | undefined; + player: Player; + }[] = []; + + const insert1Promise = new Deferred(); + const insert2Promise = new Deferred(); + + client.db.player.onInsert((ctx, player) => { + console.log('onInsert called'); + if (ctx.event.tag === 'Reducer') { + inserts.push({ reducerEvent: ctx.event.value, player }); + } else { + inserts.push({ reducerEvent: undefined, player }); + } + + if (!insert1Promise.isResolved) { + insert1Promise.resolve(); + } else { + insert2Promise.resolve(); + } + }); + + let reducerCallbackLog: { + reducerEvent: ReducerEvent<{ + name: 'CreatePlayer'; + args: CreatePlayer; + }>; + reducerArgs: any[]; + }[] = []; + client.reducers.onCreatePlayer((ctx, name: string, location: Point) => { + const reducerEvent = ctx.event; + reducerCallbackLog.push({ + reducerEvent, + reducerArgs: [name, location], + }); + }); + + const subscriptionMessage: ws.ServerMessage = + ws.ServerMessage.InitialSubscription({ + databaseUpdate: { + tables: [ + { + tableId: 35, + tableName: 'player', + numRows: BigInt(1), + updates: [ + ws.CompressableQueryUpdate.Uncompressed({ + deletes: { + sizeHint: ws.RowSizeHint.FixedSize(0), // not used + rowsData: new Uint8Array(), + }, + inserts: { + sizeHint: ws.RowSizeHint.FixedSize(0), // not used + rowsData: encodePlayer({ + ownerId: 'player-1', + name: 'drogus', + location: { x: 0, y: 0 }, + }), + }, + }), + ], + }, + ], + }, + requestId: 0, + totalHostExecutionDuration: new TimeDuration(BigInt(0)), + }); + + wsAdapter.sendToClient(subscriptionMessage); + + await Promise.race([ + insert1Promise.promise, + new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout')), 1000) + ), + ]); + + expect(inserts).toHaveLength(1); + expect(inserts[0].player.ownerId).toBe('player-1'); + expect(inserts[0].reducerEvent).toBe(undefined); + + const transactionUpdate = ws.ServerMessage.TransactionUpdate({ + status: ws.UpdateStatus.Committed({ + tables: [ + { + tableId: 35, + tableName: 'player', + numRows: BigInt(2), + updates: [ + ws.CompressableQueryUpdate.Uncompressed({ + deletes: { + sizeHint: ws.RowSizeHint.FixedSize(0), // not used + rowsData: new Uint8Array(), + }, + inserts: { + sizeHint: ws.RowSizeHint.FixedSize(0), // not used + rowsData: encodePlayer({ + ownerId: 'player-2', + name: 'drogus', + location: { x: 2, y: 3 }, + }), + }, + }), + ], + }, + ], + }), + timestamp: new Timestamp(1681391805281203n), + callerIdentity: anIdentity, + callerConnectionId: ConnectionId.random(), + reducerCall: { + reducerName: 'create_player', + reducerId: 0, + args: encodeCreatePlayerArgs('A Player', { x: 2, y: 3 }), + requestId: 0, + }, + energyQuantaUsed: { quanta: BigInt(33841000) }, + totalHostExecutionDuration: new TimeDuration(BigInt(1234567890)), + }); + wsAdapter.sendToClient(transactionUpdate); + + await Promise.race([ + insert2Promise.promise, + new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout')), 1000) + ), + ]); + + expect(inserts).toHaveLength(2); + expect(inserts[1].player.ownerId).toBe('player-2'); + expect(inserts[1].reducerEvent?.reducer.name).toBe('create_player'); + expect(inserts[1].reducerEvent?.status.tag).toBe('Committed'); + expect(inserts[1].reducerEvent?.callerIdentity).toEqual(anIdentity); + expect(inserts[1].reducerEvent?.reducer.args).toEqual({ + name: 'A Player', + location: { x: 2, y: 3 }, + }); + + expect(reducerCallbackLog).toHaveLength(1); + + expect(reducerCallbackLog[0].reducerEvent.callerIdentity).toEqual( + anIdentity + ); + }); + + test('tables should be updated before the reducer callback', async () => { + const wsAdapter = new WebsocketTestAdapter(); + let called = false; + const client = DbConnection.builder() + .withUri('ws://127.0.0.1:1234') + .withModuleName('db') + .withWSFn(wsAdapter.createWebSocketFn.bind(wsAdapter)) + .onConnect(() => { + called = true; + }) + .build(); + + await client['wsPromise']; + wsAdapter.acceptConnection(); + + let callbackLog: string[] = []; + + const updatePromise = new Deferred(); + + expect(client.db.player.count()).toBe(0); + + client.reducers.onCreatePlayer((ctx, name, location) => { + expect(client.db.player.count()).toBe(1); + updatePromise.resolve(); + }); + + const transactionUpdate = ws.ServerMessage.TransactionUpdate({ + status: ws.UpdateStatus.Committed({ + tables: [ + { + tableId: 35, + tableName: 'player', + numRows: BigInt(1), + updates: [ + ws.CompressableQueryUpdate.Uncompressed({ + deletes: { + sizeHint: ws.RowSizeHint.FixedSize(0), // not used + rowsData: new Uint8Array(), + }, + // FIXME: this test is evil: an initial subscription can never contain deletes or updates. + inserts: { + sizeHint: ws.RowSizeHint.FixedSize(0), // not used + rowsData: new Uint8Array([ + ...encodePlayer({ + ownerId: 'player-2', + name: 'foo', + location: { x: 0, y: 0 }, + }), + ]), + }, + }), + ], + }, + ], + }), + timestamp: new Timestamp(1681391805281203n), + callerIdentity: anIdentity, + callerConnectionId: ConnectionId.random(), + reducerCall: { + reducerName: 'create_player', + reducerId: 0, + args: encodeCreatePlayerArgs('A Player', { x: 2, y: 3 }), + requestId: 0, + }, + energyQuantaUsed: { quanta: BigInt(33841000) }, + totalHostExecutionDuration: new TimeDuration(BigInt(1234567890)), + }); + wsAdapter.sendToClient(transactionUpdate); + + await Promise.all([updatePromise.promise]); + }); + + test('a reducer callback should be called before the database callbacks', async () => { + const wsAdapter = new WebsocketTestAdapter(); + let called = false; + const client = DbConnection.builder() + .withUri('ws://127.0.0.1:1234') + .withModuleName('db') + .withWSFn(wsAdapter.createWebSocketFn.bind(wsAdapter)) + .onConnect(() => { + called = true; + }) + .build(); + + await client['wsPromise']; + wsAdapter.acceptConnection(); + + let callbackLog: string[] = []; + + const insertPromise = new Deferred(); + const updatePromise = new Deferred(); + + client.db.player.onInsert((ctx, player) => { + callbackLog.push('Player'); + + insertPromise.resolve(); + }); + + client.reducers.onCreatePlayer((ctx, name, location) => { + callbackLog.push('CreatePlayerReducer'); + + updatePromise.resolve(); + }); + + const transactionUpdate = ws.ServerMessage.TransactionUpdate({ + status: ws.UpdateStatus.Committed({ + tables: [ + { + tableId: 35, + tableName: 'player', + numRows: BigInt(1), + updates: [ + ws.CompressableQueryUpdate.Uncompressed({ + deletes: { + sizeHint: ws.RowSizeHint.FixedSize(0), // not used + rowsData: new Uint8Array(), + }, + // FIXME: this test is evil: an initial subscription can never contain deletes or updates. + inserts: { + sizeHint: ws.RowSizeHint.FixedSize(0), // not used + rowsData: new Uint8Array([ + ...encodePlayer({ + ownerId: 'player-2', + name: 'foo', + location: { x: 0, y: 0 }, + }), + ]), + }, + }), + ], + }, + ], + }), + timestamp: new Timestamp(1681391805281203n), + callerIdentity: anIdentity, + callerConnectionId: ConnectionId.random(), + reducerCall: { + reducerName: 'create_player', + reducerId: 0, + args: encodeCreatePlayerArgs('A Player', { x: 2, y: 3 }), + requestId: 0, + }, + energyQuantaUsed: { quanta: BigInt(33841000) }, + totalHostExecutionDuration: new TimeDuration(BigInt(1234567890)), + }); + wsAdapter.sendToClient(transactionUpdate); + + await Promise.all([insertPromise.promise, updatePromise.promise]); + + expect(callbackLog).toEqual(['CreatePlayerReducer', 'Player']); + }); + + test('it calls onUpdate callback when a record is added with a subscription update and then with a transaction update when the PK is of type Identity', async () => { + const wsAdapter = new WebsocketTestAdapter(); + let called = false; + const client = DbConnection.builder() + .withUri('ws://127.0.0.1:1234') + .withModuleName('db') + .withWSFn(wsAdapter.createWebSocketFn.bind(wsAdapter)) + .onConnect(() => { + called = true; + }) + .build(); + + await client['wsPromise']; + wsAdapter.acceptConnection(); + + const tokenMessage = ws.ServerMessage.IdentityToken({ + identity: Identity.fromString( + '0000000000000000000000000000000000000000000000000000000000000069' + ), + token: 'a-token', + connectionId: ConnectionId.random(), + }); + wsAdapter.sendToClient(tokenMessage); + + const update1Promise = new Deferred(); + const initialInsertPromise = new Deferred(); + const userIdentity = Identity.fromString( + '41db74c20cdda916dd2637e5a11b9f31eb1672249aa7172f7e22b4043a6a9008' + ); + + const initialUser: User = { + identity: userIdentity, + username: 'originalName', + }; + const updatedUser: User = { + identity: userIdentity, + username: 'newName', + }; + + const updates: { + oldUser: User; + newUser: User; + }[] = []; + client.db.user.onInsert((ctx, user) => { + initialInsertPromise.resolve(); + console.log('got insert'); + }); + client.db.user.onUpdate((_ctx, oldUser, newUser) => { + updates.push({ + oldUser, + newUser, + }); + update1Promise.resolve(); + }); + + const subscriptionMessage = ws.ServerMessage.InitialSubscription({ + databaseUpdate: { + tables: [ + { + tableId: 35, + tableName: 'user', + numRows: BigInt(1), + updates: [ + // pgoldman 2024-06-25: This is weird, `InitialSubscription`s aren't supposed to contain deletes or updates. + ws.CompressableQueryUpdate.Uncompressed({ + deletes: { + sizeHint: ws.RowSizeHint.FixedSize(0), // not used + rowsData: new Uint8Array([]), + }, + inserts: { + sizeHint: ws.RowSizeHint.FixedSize(0), // not used + rowsData: new Uint8Array([...encodeUser(initialUser)]), + }, + }), + ], + }, + ], + }, + requestId: 0, + totalHostExecutionDuration: new TimeDuration(BigInt(1234567890)), + }); + + wsAdapter.sendToClient(subscriptionMessage); + + // await update1Promise.promise; + await initialInsertPromise.promise; + console.log('First insert is done'); + + const transactionUpdate = ws.ServerMessage.TransactionUpdate({ + status: ws.UpdateStatus.Committed({ + tables: [ + { + tableId: 35, + tableName: 'user', + numRows: BigInt(1), + updates: [ + ws.CompressableQueryUpdate.Uncompressed({ + deletes: { + sizeHint: ws.RowSizeHint.FixedSize(0), // not used + rowsData: new Uint8Array([...encodeUser(initialUser)]), + }, + // FIXME: this test is evil: an initial subscription can never contain deletes or updates. + inserts: { + sizeHint: ws.RowSizeHint.FixedSize(0), // not used + rowsData: new Uint8Array([...encodeUser(updatedUser)]), + }, + }), + ], + }, + ], + }), + timestamp: new Timestamp(1681391805281203n), + callerIdentity: anIdentity, + callerConnectionId: ConnectionId.random(), + reducerCall: { + reducerName: 'create_player', + reducerId: 0, + args: encodeCreatePlayerArgs('A Player', { x: 2, y: 3 }), + requestId: 0, + }, + energyQuantaUsed: { quanta: BigInt(33841000) }, + totalHostExecutionDuration: new TimeDuration(BigInt(1234567890)), + }); + + console.log('Sending transaction update'); + wsAdapter.sendToClient(transactionUpdate); + + await update1Promise.promise; + + expect(updates).toHaveLength(1); + expect(updates[0]['oldUser'].username).toBe(initialUser.username); + expect(updates[0]['newUser'].username).toBe(updatedUser.username); + + console.log('Users: ', [...client.db.user.iter()]); + expect(client.db.user.count()).toBe(1); + }); + + test('Filtering works', async () => { + const wsAdapter = new WebsocketTestAdapter(); + const client = DbConnection.builder() + .withUri('ws://127.0.0.1:1234') + .withModuleName('db') + .withWSFn(wsAdapter.createWebSocketFn.bind(wsAdapter)) + .build(); + await client['wsPromise']; + const user1 = { identity: bobIdentity, username: 'bob' }; + const user2 = { + identity: sallyIdentity, + username: 'sally', + }; + const binary = [...encodeUser(user1)].concat([...encodeUser(user2)]); + const transactionUpdate = ws.ServerMessage.TransactionUpdate({ + status: ws.UpdateStatus.Committed({ + tables: [ + { + tableId: 35, + tableName: 'user', + numRows: BigInt(1), + updates: [ + ws.CompressableQueryUpdate.Uncompressed({ + deletes: { + sizeHint: ws.RowSizeHint.FixedSize(0), // not used + rowsData: new Uint8Array([]), + }, + // FIXME: this test is evil: an initial subscription can never contain deletes or updates. + inserts: { + sizeHint: ws.RowSizeHint.FixedSize(0), // not used + rowsData: new Uint8Array(binary), + }, + }), + ], + }, + ], + }), + timestamp: new Timestamp(1681391805281203n), + callerIdentity: anIdentity, + callerConnectionId: ConnectionId.random(), + reducerCall: { + reducerName: 'create_player', + reducerId: 0, + args: encodeCreatePlayerArgs('A Player', { x: 2, y: 3 }), + requestId: 0, + }, + energyQuantaUsed: { quanta: BigInt(33841000) }, + totalHostExecutionDuration: new TimeDuration(BigInt(1234567890)), + }); + const gotAllInserts = new Deferred(); + var inserts = 0; + client.db.user.onInsert((ctx, user) => { + inserts++; + if (inserts == 2) { + gotAllInserts.resolve(); + } + }); + wsAdapter.sendToClient(transactionUpdate); + await gotAllInserts.promise; + + const filteredUser = client.db.user.identity.find(sallyIdentity); + expect(filteredUser).not.toBeUndefined; + expect(filteredUser!.username).toBe('sally'); + expect(client.db.user.count()).toBe(2); + }); +}); diff --git a/sdks/typescript/packages/sdk/tests/table_cache.test.ts b/sdks/typescript/packages/sdk/tests/table_cache.test.ts new file mode 100644 index 00000000000..1e666af4e45 --- /dev/null +++ b/sdks/typescript/packages/sdk/tests/table_cache.test.ts @@ -0,0 +1,782 @@ +import { Operation, TableCache } from '../src/table_cache'; +import type { TableRuntimeTypeInfo } from '../src/spacetime_module'; +import { describe, expect, test } from 'vitest'; + +import { Player } from '@clockworklabs/test-app/src/module_bindings'; + +import { AlgebraicType, ProductTypeElement } from '../src/algebraic_type'; + +interface ApplyOperations { + ops: Operation[]; + ctx: any; +} + +interface CallbackEvent { + type: 'insert' | 'delete' | 'update'; + ctx: any; + row: any; + oldRow?: any; // Only there for updates. +} + +function insertEvent(row: any, ctx: any = {}): CallbackEvent { + return { + type: 'insert', + ctx, + row, + }; +} + +function updateEvent(oldRow: any, row: any, ctx: any = {}): CallbackEvent { + return { + type: 'update', + ctx, + row, + oldRow, + }; +} + +function deleteEvent(row: any, ctx: any = {}): CallbackEvent { + return { + type: 'delete', + ctx, + row, + }; +} + +interface AssertionInput { + // The state of the table cache. + tableCache: TableCache; + // The sequence of callbacks that were fired from the last applyOperations. + callbackHistory: CallbackEvent[]; +} + +type Assertion = (AssertionInput) => void; + +interface TestStep { + // The operations to apply. + ops: ApplyOperations; + // The assertions to make after applying the operations. + assertions: Assertion[]; +} + +function runTest(tableCache: TableCache, testSteps: TestStep[]) { + const callbackHistory: CallbackEvent[] = []; + tableCache.onInsert((ctx, row) => { + callbackHistory.push({ + type: 'insert', + ctx, + row, + }); + }); + tableCache.onDelete((ctx, row) => { + callbackHistory.push({ + type: 'delete', + ctx, + row, + }); + }); + tableCache.onUpdate((ctx, oldRow, row) => { + callbackHistory.push({ + type: 'update', + ctx, + row, + oldRow, + }); + }); + + for (const step of testSteps) { + const { ops: applyOperations, assertions } = step; + const { ops, ctx } = applyOperations; + const callbacks = tableCache.applyOperations(ops, ctx); + callbacks.forEach(cb => cb.cb()); + for (const assertion of assertions) { + assertion({ tableCache, callbackHistory }); + } + // Clear the callback history for the next step. + callbackHistory.length = 0; + } +} + +describe('TableCache', () => { + describe('Unindexed player table', () => { + const pointType = AlgebraicType.createProductType([ + new ProductTypeElement('x', AlgebraicType.createU16Type()), + new ProductTypeElement('y', AlgebraicType.createU16Type()), + ]); + const playerType = AlgebraicType.createProductType([ + new ProductTypeElement('ownerId', AlgebraicType.createStringType()), + new ProductTypeElement('name', AlgebraicType.createStringType()), + new ProductTypeElement('location', pointType), + ]); + const tableTypeInfo: TableRuntimeTypeInfo = { + tableName: 'player', + rowType: playerType, + }; + const newTable = () => new TableCache(tableTypeInfo); + const mkOperation = (type: 'insert' | 'delete', row: Player) => { + let rowId = tableTypeInfo.rowType.intoMapKey(row); + return { + type, + rowId, + row, + }; + }; + + test('Insert one', () => { + const tableCache = newTable(); + const steps: TestStep[] = []; + let player = { + ownerId: '1', + name: 'Player 1', + location: { + x: 1, + y: 2, + }, + }; + steps.push({ + ops: { + ops: [mkOperation('insert', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter().length).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory.length).toBe(1); + expect(callbackHistory[0].type).toBe('insert'); + expect(callbackHistory[0].row).toEqual(player); + }, + ], + }); + runTest(tableCache, steps); + }); + + test('Inserting one twice only triggers one event', () => { + const tableCache = newTable(); + const steps: TestStep[] = []; + let player = { + ownerId: '1', + name: 'Player 1', + location: { + x: 1, + y: 2, + }, + }; + steps.push({ + ops: { + ops: [mkOperation('insert', player), mkOperation('insert', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter().length).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory.length).toBe(1); + expect(callbackHistory[0].type).toBe('insert'); + expect(callbackHistory[0].row).toEqual(player); + }, + ], + }); + runTest(tableCache, steps); + }); + + test('Insert dupe is a noop', () => { + const tableCache = newTable(); + const steps: TestStep[] = []; + let player = { + ownerId: '1', + name: 'Player 1', + location: { + x: 1, + y: 2, + }, + }; + steps.push({ + ops: { + ops: [mkOperation('insert', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter().length).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory.length).toBe(1); + expect(callbackHistory[0].type).toBe('insert'); + expect(callbackHistory[0].row).toEqual(player); + }, + ], + }); + steps.push({ + ops: { + ops: [mkOperation('insert', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter().length).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory.length).toBe(0); + }, + ], + }); + runTest(tableCache, steps); + }); + + test('Insert once and delete', () => { + const tableCache = newTable(); + const steps: TestStep[] = []; + let player = { + ownerId: '1', + name: 'Player 1', + location: { + x: 1, + y: 2, + }, + }; + steps.push({ + ops: { + ops: [mkOperation('insert', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory.length).toBe(1); + expect(callbackHistory[0].type).toBe('insert'); + expect(callbackHistory[0].row).toEqual(player); + }, + ], + }); + steps.push({ + ops: { + ops: [mkOperation('delete', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(0); + expect(callbackHistory.length).toBe(1); + expect(callbackHistory[0].type).toBe('delete'); + expect(callbackHistory[0].row).toEqual(player); + }, + ], + }); + runTest(tableCache, steps); + }); + + test('Insert twice and delete', () => { + const tableCache = newTable(); + const steps: TestStep[] = []; + let mkPlayer = () => ({ + ownerId: '1', + name: 'Player 1', + location: { + x: 1, + y: 2, + }, + }); + let player = { + ownerId: '1', + name: 'Player 1', + location: { + x: 1, + y: 2, + }, + }; + steps.push({ + ops: { + ops: [ + mkOperation('insert', mkPlayer()), + mkOperation('insert', player), + ], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory.length).toBe(1); + expect(callbackHistory[0].type).toBe('insert'); + expect(callbackHistory[0].row).toEqual(player); + }, + ], + }); + steps.push({ + ops: { + ops: [mkOperation('delete', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + // We still have one reference left, so it isn't actually deleted. + expect(tableCache.count()).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory.length).toBe(0); + }, + ], + }); + steps.push({ + ops: { + ops: [mkOperation('delete', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + // Now it is actually deleted. + expect(tableCache.count()).toBe(0); + expect(callbackHistory.length).toBe(1); + expect(callbackHistory[0].type).toBe('delete'); + expect(callbackHistory[0].row).toEqual(player); + }, + ], + }); + // Now we are going to insert again, so we can delete both refs at once. + steps.push({ + ops: { + ops: [mkOperation('insert', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory).toEqual([insertEvent(player)]); + }, + ], + }); + steps.push({ + ops: { + ops: [mkOperation('insert', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory).toEqual([]); + }, + ], + }); + steps.push({ + ops: { + ops: [mkOperation('delete', player), mkOperation('delete', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(0); + expect(callbackHistory).toEqual([deleteEvent(mkPlayer())]); + }, + ], + }); + runTest(tableCache, steps); + }); + + test('Insert one', () => { + const tableCache = newTable(); + let op = mkOperation('insert', { + ownerId: '1', + name: 'Player 1', + location: { + x: 1, + y: 2, + }, + }); + let rowsInserted = 0; + let callbacks = tableCache.applyOperations([op], {} as any); + tableCache.onInsert((ctx, row) => { + rowsInserted++; + expect(row).toEqual(op.row); + }); + expect(callbacks.length).toBe(1); + expect(tableCache.count()).toBe(1); + callbacks.forEach(cb => { + cb.cb(); + }); + expect(rowsInserted).toBe(1); + }); + }); + describe('Indexed player table', () => { + const pointType = AlgebraicType.createProductType([ + new ProductTypeElement('x', AlgebraicType.createU16Type()), + new ProductTypeElement('y', AlgebraicType.createU16Type()), + ]); + const playerType = AlgebraicType.createProductType([ + new ProductTypeElement('ownerId', AlgebraicType.createStringType()), + new ProductTypeElement('name', AlgebraicType.createStringType()), + new ProductTypeElement('location', pointType), + ]); + const tableTypeInfo: TableRuntimeTypeInfo = { + tableName: 'player', + rowType: playerType, + primaryKeyInfo: { + colName: 'ownerId', + colType: playerType.product.elements[0].algebraicType, + }, + }; + const newTable = () => new TableCache(tableTypeInfo); + const mkOperation = (type: 'insert' | 'delete', row: Player) => { + let rowId = tableTypeInfo.primaryKeyInfo!.colType.intoMapKey( + row['ownerId'] + ); + return { + type, + rowId, + row, + }; + }; + + test('Insert one', () => { + const tableCache = newTable(); + const steps: TestStep[] = []; + let player = { + ownerId: '1', + name: 'Player 1', + location: { + x: 1, + y: 2, + }, + }; + steps.push({ + ops: { + ops: [mkOperation('insert', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter().length).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory.length).toBe(1); + expect(callbackHistory[0].type).toBe('insert'); + expect(callbackHistory[0].row).toEqual(player); + }, + ], + }); + runTest(tableCache, steps); + }); + + test('Inserting one twice only triggers one event', () => { + const tableCache = newTable(); + const steps: TestStep[] = []; + let player = { + ownerId: '1', + name: 'Player 1', + location: { + x: 1, + y: 2, + }, + }; + steps.push({ + ops: { + ops: [mkOperation('insert', player), mkOperation('insert', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter().length).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory.length).toBe(1); + expect(callbackHistory[0].type).toBe('insert'); + expect(callbackHistory[0].row).toEqual(player); + }, + ], + }); + runTest(tableCache, steps); + }); + + test('Insert dupe is a noop', () => { + const tableCache = newTable(); + const steps: TestStep[] = []; + let player = { + ownerId: '1', + name: 'Player 1', + location: { + x: 1, + y: 2, + }, + }; + steps.push({ + ops: { + ops: [mkOperation('insert', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter().length).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory.length).toBe(1); + expect(callbackHistory[0].type).toBe('insert'); + expect(callbackHistory[0].row).toEqual(player); + }, + ], + }); + steps.push({ + ops: { + ops: [mkOperation('insert', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter().length).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory.length).toBe(0); + }, + ], + }); + runTest(tableCache, steps); + }); + + test('Insert once and delete', () => { + const tableCache = newTable(); + const steps: TestStep[] = []; + let player = { + ownerId: '1', + name: 'Player 1', + location: { + x: 1, + y: 2, + }, + }; + steps.push({ + ops: { + ops: [mkOperation('insert', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory.length).toBe(1); + expect(callbackHistory[0].type).toBe('insert'); + expect(callbackHistory[0].row).toEqual(player); + }, + ], + }); + steps.push({ + ops: { + ops: [mkOperation('delete', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(0); + expect(callbackHistory.length).toBe(1); + expect(callbackHistory[0].type).toBe('delete'); + expect(callbackHistory[0].row).toEqual(player); + }, + ], + }); + runTest(tableCache, steps); + }); + + test('Update smoke test', () => { + const tableCache = newTable(); + const steps: TestStep[] = []; + let mkPlayer = (name: string) => ({ + ownerId: '1', + name: name, + location: { + x: 1, + y: 2, + }, + }); + steps.push({ + ops: { + ops: [mkOperation('insert', mkPlayer('jeff'))], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter()[0]).toEqual(mkPlayer('jeff')); + expect(callbackHistory).toEqual([insertEvent(mkPlayer('jeff'))]); + }, + ], + }); + steps.push({ + ops: { + ops: [ + mkOperation('delete', mkPlayer('jeff')), + mkOperation('insert', mkPlayer('jeffv2')), + ], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter()[0]).toEqual(mkPlayer('jeffv2')); + expect(callbackHistory).toEqual([ + updateEvent(mkPlayer('jeff'), mkPlayer('jeffv2')), + ]); + }, + ], + }); + runTest(tableCache, steps); + }); + + test('Insert twice and delete', () => { + const tableCache = newTable(); + const steps: TestStep[] = []; + let mkPlayer = () => ({ + ownerId: '1', + name: 'Player 1', + location: { + x: 1, + y: 2, + }, + }); + let player = { + ownerId: '1', + name: 'Player 1', + location: { + x: 1, + y: 2, + }, + }; + steps.push({ + ops: { + ops: [ + mkOperation('insert', mkPlayer()), + mkOperation('insert', player), + ], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory.length).toBe(1); + expect(callbackHistory[0].type).toBe('insert'); + expect(callbackHistory[0].row).toEqual(player); + }, + ], + }); + steps.push({ + ops: { + ops: [mkOperation('delete', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + // We still have one reference left, so it isn't actually deleted. + expect(tableCache.count()).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory.length).toBe(0); + }, + ], + }); + steps.push({ + ops: { + ops: [mkOperation('delete', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + // Now it is actually deleted. + expect(tableCache.count()).toBe(0); + expect(callbackHistory.length).toBe(1); + expect(callbackHistory[0].type).toBe('delete'); + expect(callbackHistory[0].row).toEqual(player); + }, + ], + }); + // Now we are going to insert again, so we can delete both refs at once. + steps.push({ + ops: { + ops: [mkOperation('insert', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory).toEqual([insertEvent(player)]); + }, + ], + }); + steps.push({ + ops: { + ops: [mkOperation('insert', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(1); + expect(tableCache.iter()[0]).toEqual(player); + expect(callbackHistory).toEqual([]); + }, + ], + }); + steps.push({ + ops: { + ops: [mkOperation('delete', player), mkOperation('delete', player)], + ctx: {} as any, + }, + assertions: [ + ({ tableCache, callbackHistory }) => { + expect(tableCache.count()).toBe(0); + expect(callbackHistory).toEqual([deleteEvent(mkPlayer())]); + }, + ], + }); + runTest(tableCache, steps); + }); + + test('Insert one', () => { + const tableCache = newTable(); + let op = mkOperation('insert', { + ownerId: '1', + name: 'Player 1', + location: { + x: 1, + y: 2, + }, + }); + let rowsInserted = 0; + let callbacks = tableCache.applyOperations([op], {} as any); + tableCache.onInsert((ctx, row) => { + rowsInserted++; + expect(row).toEqual(op.row); + }); + expect(callbacks.length).toBe(1); + expect(tableCache.count()).toBe(1); + callbacks.forEach(cb => { + cb.cb(); + }); + expect(rowsInserted).toBe(1); + }); + }); + + const pointType = AlgebraicType.createProductType([ + new ProductTypeElement('x', AlgebraicType.createU16Type()), + new ProductTypeElement('y', AlgebraicType.createU16Type()), + ]); + const playerType = AlgebraicType.createProductType([ + new ProductTypeElement('ownerId', AlgebraicType.createStringType()), + new ProductTypeElement('name', AlgebraicType.createStringType()), + new ProductTypeElement('location', pointType), + ]); + + test('should be empty on creation', () => { + const tableTypeInfo: TableRuntimeTypeInfo = { + tableName: 'player', + rowType: playerType, + primaryKeyInfo: { + colName: 'ownerId', + colType: playerType.product.elements[0].algebraicType, + }, + }; + const tableCache = new TableCache(tableTypeInfo); + expect(tableCache.count()).toBe(0); + tableCache.applyOperations; + }); +}); diff --git a/sdks/typescript/packages/sdk/tests/version.test.ts b/sdks/typescript/packages/sdk/tests/version.test.ts new file mode 100644 index 00000000000..08d6adc6130 --- /dev/null +++ b/sdks/typescript/packages/sdk/tests/version.test.ts @@ -0,0 +1,185 @@ +import { describe, expect, test } from 'vitest'; +import { + SemanticVersion, + _MINIMUM_CLI_VERSION, + ensureMinimumVersionOrThrow, +} from '../src/version.ts'; + +describe('SemanticVersion', () => { + describe('Minimum version check', () => { + test('older versions throw an error', () => { + let olderVersions: string[] = ['0.0.1', '0.1.0', '1.1.0']; + if (_MINIMUM_CLI_VERSION.major > 0) { + let olderVersion = _MINIMUM_CLI_VERSION.clone(); + olderVersion.major -= 1; + olderVersions.push(olderVersion.toString()); + } + if (_MINIMUM_CLI_VERSION.minor > 0) { + let olderVersion = _MINIMUM_CLI_VERSION.clone(); + olderVersion.minor -= 1; + olderVersions.push(olderVersion.toString()); + } + if (_MINIMUM_CLI_VERSION.patch > 0) { + let olderVersion = _MINIMUM_CLI_VERSION.clone(); + olderVersion.patch -= 1; + olderVersions.push(olderVersion.toString()); + } + if (!_MINIMUM_CLI_VERSION.preRelease) { + let olderVersion = _MINIMUM_CLI_VERSION.clone(); + olderVersion.preRelease = ['alpha']; + olderVersions.push(olderVersion.toString()); + } + let olderVersion = _MINIMUM_CLI_VERSION.clone(); + if (olderVersion.preRelease != null) { + if (typeof olderVersion.preRelease[0] === 'number') { + olderVersion.preRelease[0] = olderVersion.preRelease[0] - 1; + } else { + olderVersion.preRelease[0] += 'alpha'; + } + } + const errorRegexp = new RegExp( + '.*generated with an incompatible version.*' + ); + for (const versionString of olderVersions) { + expect( + () => ensureMinimumVersionOrThrow(versionString), + `Checking ${versionString}` + ).toThrow(errorRegexp); + } + }); + + test('newer versions do not throw', () => { + let newerVersions: string[] = [_MINIMUM_CLI_VERSION.toString()]; + let newVersion = _MINIMUM_CLI_VERSION.clone(); + newVersion.major += 1; + newerVersions.push(newVersion.toString()); + newVersion = _MINIMUM_CLI_VERSION.clone(); + newVersion.minor += 1; + newerVersions.push(newVersion.toString()); + newVersion = _MINIMUM_CLI_VERSION.clone(); + newVersion.patch += 1; + newerVersions.push(newVersion.toString()); + newVersion = _MINIMUM_CLI_VERSION.clone(); + if (newVersion.preRelease != null) { + newVersion.preRelease = null; + newerVersions.push(newVersion.toString()); + } + const errorRegexp = new RegExp( + '.*generated with an incompatible version.*' + ); + for (const versionString of newerVersions) { + expect( + () => ensureMinimumVersionOrThrow(versionString), + `Checking ${versionString}` + ).not.toThrow(); + } + }); + }); + describe('Parsing semantic version strings', () => { + test('valid versions', () => { + // This is a dummy test to ensure that the test suite runs + // and that the TableCache is working as expected. + // You can add more tests here to cover different scenarios. + //parseVersionString('1.0.0'); + let tests: [string, SemanticVersion][] = [ + ['1.0.0', new SemanticVersion(1, 0, 0)], + ['1.0.0-alpha', new SemanticVersion(1, 0, 0, ['alpha'])], + ['1.0.0-alpha.1', new SemanticVersion(1, 0, 0, ['alpha', 1])], + [ + '1.0.0-alpha.20.beta', + new SemanticVersion(1, 0, 0, ['alpha', 20, 'beta']), + ], + ['1.0.0-alpha.beta', new SemanticVersion(1, 0, 0, ['alpha', 'beta'])], + ['0.2.1', new SemanticVersion(0, 2, 1)], + ['0.0.0', new SemanticVersion(0, 0, 0)], + ['10.2.1', new SemanticVersion(10, 2, 1)], + [ + '1.0.0+20130313144700', + new SemanticVersion(1, 0, 0, null, '20130313144700'), + ], + ['1.0.0-alpha+001', new SemanticVersion(1, 0, 0, ['alpha'], '001')], + [ + '1.0.0-alpha.beta+exp.sha.5114f85', + new SemanticVersion(1, 0, 0, ['alpha', 'beta'], 'exp.sha.5114f85'), + ], + [ + '1.0.0+exp.sha.5114f85', + new SemanticVersion(1, 0, 0, null, 'exp.sha.5114f85'), + ], + ]; + for (const [versionString, expectedVersion] of tests) { + const parsedVersion = SemanticVersion.parseVersionString(versionString); + expect(parsedVersion, `Parsing ${versionString}`).toEqual( + expectedVersion + ); + } + }); + + test('invalid version strings should throw an error', () => { + let invalidTests: string[] = [ + '1.0', // Missing patch version + '1', // Missing minor and patch versions + '1.0.0-', // Trailing hyphen + '1.0.0+', // Trailing plus + '1.0.0-alpha..1', // Double dots in pre-release + '1.0.0+build..info', // Double dots in build metadata + '1.0.0-alpha!', // Invalid character in pre-release + '1.0.0+build!', // Invalid character in build metadata + 'abc.def.ghi', // Completely invalid format + '', // Empty string + ]; + for (const versionString of invalidTests) { + expect(() => SemanticVersion.parseVersionString(versionString)).toThrow( + `Invalid version string: ${versionString}` + ); + + // Checking the minimum version should also fail. + expect(() => ensureMinimumVersionOrThrow(versionString)).toThrow( + `Invalid version string: ${versionString}` + ); + } + }); + }); + describe('Comparing SemanticVersions', () => { + function normalizedCompare( + version1: SemanticVersion, + version2: SemanticVersion + ): number { + let c = version1.compare(version2); + if (c < 0) { + return -1; + } + if (c > 0) { + return 1; + } + return 0; + } + test('test comparison order', () => { + let cases: [string, string, number][] = [ + ['1.0.0', '1.0.0', 0], + ['1.0.0', '1.0.1', -1], + ['1.10.0', '1.2.1', 1], + ['1.2.1', '1.10.0', -1], + ['1.0.1', '1.0.0', 1], + ['1.0.0-alpha', '1.0.0-alpha', 0], + ['1.0.0-alpha', '1.0.0-beta', -1], + ['1.0.0-beta', '1.0.0-alpha', 1], + ['1.0.0-alpha', '1.0.0-alpha.beta', -1], + ['1.0.0-1', '1.0.0-alpha.beta', -1], + ['1.0.0-alpha.1', '1.0.0-alpha.2', -1], + ['1.0.0-alpha.beta', '1.0.0-alpha', 1], + ['2.0.0-alpha.beta', '2.0.0-alpha.beta', 0], + ['2.0.0-alpha.beta+001', '2.0.0-alpha.beta+001', 0], + ['2.0.0-alpha.beta+001', '2.0.0-alpha.beta+002', 0], // Build tags don't affect comparison + ]; + for (const [left, right, expectedComparison] of cases) { + const parsedLeft = SemanticVersion.parseVersionString(left); + const parsedRight = SemanticVersion.parseVersionString(right); + expect( + normalizedCompare(parsedLeft, parsedRight), + 'Comparing ' + left + ' and ' + right + ).toEqual(expectedComparison); + } + }); + }); +}); diff --git a/sdks/typescript/packages/sdk/tsconfig.json b/sdks/typescript/packages/sdk/tsconfig.json new file mode 100644 index 00000000000..8c28df00aa8 --- /dev/null +++ b/sdks/typescript/packages/sdk/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "declaration": true, + "emitDeclarationOnly": false, + "noEmit": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "verbatimModuleSyntax": true, + "allowImportingTsExtensions": true, + "strict": true, + "noImplicitAny": false, + "moduleResolution": "Bundler", + "allowSyntheticDefaultImports": true, + "isolatedDeclarations": true, + "isolatedModules": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "**/__tests__/*", "dist/**/*"] +} diff --git a/sdks/typescript/packages/test-app/.gitignore b/sdks/typescript/packages/test-app/.gitignore new file mode 100644 index 00000000000..a547bf36d8d --- /dev/null +++ b/sdks/typescript/packages/test-app/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/sdks/typescript/packages/test-app/CHANGELOG.md b/sdks/typescript/packages/test-app/CHANGELOG.md new file mode 100644 index 00000000000..f5ae6c467c6 --- /dev/null +++ b/sdks/typescript/packages/test-app/CHANGELOG.md @@ -0,0 +1,29 @@ +# @clockworklabs/test-app + +## 0.0.1 + +### Patch Changes + +- Updated dependencies [[`cf7b7d8`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/cf7b7d89a1547fb3863f6641f5b2eb40a27c05d8), [`941cf4e`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/941cf4eba6b7df934d74696b373b89cc62764673), [`a501f5c`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/a501f5ccf9a0a926eb4f345ddeb01ffcb872d67e), [`9032269`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/9032269004d4dae587c39ccd85da0a32fb9a0114), [`6547882`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/6547882bb28ed9a1ca436335745e9997328026ff), [`5d7304b`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/5d7304bd3e05dd7a032cfb7069aab97b881f0179)]: + - @clockworklabs/spacetimedb-sdk@1.2.0 + +## 0.0.3-rc1.0 + +### Patch Changes + +- Updated dependencies [[`cf7b7d8`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/cf7b7d89a1547fb3863f6641f5b2eb40a27c05d8), [`a501f5c`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/a501f5ccf9a0a926eb4f345ddeb01ffcb872d67e), [`9032269`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/9032269004d4dae587c39ccd85da0a32fb9a0114), [`6547882`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/6547882bb28ed9a1ca436335745e9997328026ff), [`5d7304b`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/5d7304bd3e05dd7a032cfb7069aab97b881f0179)]: + - @clockworklabs/spacetimedb-sdk@1.0.0-rc1.0 + +## 0.0.2 + +### Patch Changes + +- Updated dependencies [[`2f6c82c`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/2f6c82c724b9f9407c7bedee13252ca8ffab8f7d), [`b9db9b6`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/b9db9b6e46d8c98b29327d97c12c07b7a2fc96bf), [`79c278b`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/79c278be71b2dfd82106ada983fd81d395b1d912)]: + - @clockworklabs/spacetimedb-sdk@0.12.1 + +## 0.0.1 + +### Patch Changes + +- Updated dependencies [[`5adb557`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/5adb55776c81d0760cf0268df0fa5dee600f0ef8), [`ab1f463`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/ab1f463d7da6e530a6cd47e2433141bfd16addd1), [`b8c944c`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/b8c944cd23d3b53c72131803a775127bf0a95213), [`17227c0`](https://github.com/clockworklabs/spacetimedb-typescript-sdk/commit/17227c0f65def3a9d5e767756ccf46777210041a)]: + - @clockworklabs/spacetimedb-sdk@0.12.0 diff --git a/sdks/typescript/packages/test-app/README.md b/sdks/typescript/packages/test-app/README.md new file mode 100644 index 00000000000..3feaa9d74ce --- /dev/null +++ b/sdks/typescript/packages/test-app/README.md @@ -0,0 +1,30 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default { + // other rules... + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: __dirname, + }, +}; +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/sdks/typescript/packages/test-app/index.html b/sdks/typescript/packages/test-app/index.html new file mode 100644 index 00000000000..e4b78eae123 --- /dev/null +++ b/sdks/typescript/packages/test-app/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/sdks/typescript/packages/test-app/package.json b/sdks/typescript/packages/test-app/package.json new file mode 100644 index 00000000000..a98f2642634 --- /dev/null +++ b/sdks/typescript/packages/test-app/package.json @@ -0,0 +1,31 @@ +{ + "name": "@clockworklabs/test-app", + "private": true, + "version": "0.0.1", + "type": "module", + "files": [ + "src" + ], + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview", + "spacetime:generate-bindings": "spacetime generate --lang typescript --out-dir src/module_bindings --project-path server", + "spacetime:start": "spacetime start server", + "spacetime:publish:local": "spacetime publish game --project-path server --server local", + "spacetime:publish": "spacetime publish game --project-path server --server testnet" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "@clockworklabs/spacetimedb-sdk": "workspace:*" + }, + "devDependencies": { + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "typescript": "^5.2.2", + "vite": "^5.3.4" + } +} diff --git a/sdks/typescript/packages/test-app/server/.gitignore b/sdks/typescript/packages/test-app/server/.gitignore new file mode 100644 index 00000000000..31b13f058aa --- /dev/null +++ b/sdks/typescript/packages/test-app/server/.gitignore @@ -0,0 +1,17 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# Spacetime ignore +/.spacetime \ No newline at end of file diff --git a/sdks/typescript/packages/test-app/server/Cargo.toml b/sdks/typescript/packages/test-app/server/Cargo.toml new file mode 100644 index 00000000000..9b278585109 --- /dev/null +++ b/sdks/typescript/packages/test-app/server/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "spacetime-module" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb = "1.2.0" +log = "0.4" +anyhow = "1.0" diff --git a/sdks/typescript/packages/test-app/server/src/lib.rs b/sdks/typescript/packages/test-app/server/src/lib.rs new file mode 100644 index 00000000000..4fbd2086d21 --- /dev/null +++ b/sdks/typescript/packages/test-app/server/src/lib.rs @@ -0,0 +1,38 @@ +use spacetimedb::{reducer, table, Identity, ReducerContext, SpacetimeType, Table}; + +#[table(name = player, public)] +pub struct Player { + #[primary_key] + owner_id: String, + name: String, + location: Point, +} + +#[derive(SpacetimeType)] +pub struct Point { + pub x: u16, + pub y: u16, +} + +#[table(name = user, public)] +pub struct User { + #[primary_key] + pub identity: Identity, + pub username: String, +} + +#[table(name = unindexed_player, public)] +pub struct UnindexedPlayer { + owner_id: String, + name: String, + location: Point, +} + +#[reducer] +pub fn create_player(ctx: &ReducerContext, name: String, location: Point) { + ctx.db.player().insert(Player { + owner_id: ctx.sender.to_hex().to_string(), + name, + location, + }); +} diff --git a/sdks/typescript/packages/test-app/src/.gitattributes b/sdks/typescript/packages/test-app/src/.gitattributes new file mode 100644 index 00000000000..f4d6534ab2c --- /dev/null +++ b/sdks/typescript/packages/test-app/src/.gitattributes @@ -0,0 +1 @@ +/module_bindings/** linguist-generated=true diff --git a/sdks/typescript/packages/test-app/src/App.css b/sdks/typescript/packages/test-app/src/App.css new file mode 100644 index 00000000000..b9d355df2a5 --- /dev/null +++ b/sdks/typescript/packages/test-app/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/sdks/typescript/packages/test-app/src/App.tsx b/sdks/typescript/packages/test-app/src/App.tsx new file mode 100644 index 00000000000..725c78fbb06 --- /dev/null +++ b/sdks/typescript/packages/test-app/src/App.tsx @@ -0,0 +1,57 @@ +import { DbConnection } from './module_bindings'; +import { useEffect, useState } from 'react'; +import './App.css'; + +function App() { + const [connection] = useState(() => + DbConnection.builder() + .withUri('ws://localhost:3000') + .withModuleName('game') + .withLightMode(true) + .onDisconnect(() => { + console.log('disconnected'); + }) + .onConnectError(() => { + console.log('client_error'); + }) + .onConnect((conn, identity, _token) => { + console.log( + 'Connected to SpacetimeDB with identity:', + identity.toHexString() + ); + + conn.subscriptionBuilder().subscribe('SELECT * FROM player'); + }) + .withToken( + 'eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIwMUpCQTBYRzRESFpIWUdQQk5GRFk5RDQ2SiIsImlzcyI6Imh0dHBzOi8vYXV0aC5zdGFnaW5nLnNwYWNldGltZWRiLmNvbSIsImlhdCI6MTczMDgwODUwNSwiZXhwIjoxNzkzODgwNTA1fQ.kGM4HGX0c0twL8NJoSQowzSZa8dc2Ogc-fsvaDK7otUrcdGFsZ3KsNON2eNkFh73FER0hl55_eJStr2tgoPwfTyl_v_TqkY45iUOUlLmHfB-X42cMzpE7PXbR_PKYcp-P-Wa4jGtVl4oF7CvdGKxlhIYEk3e0ElQlA9ThnZN4IEciYV0vwAXGqbaO9SOG8jbrmlmfN7oKgl02EgpodEAHTrnB2mD1qf1YyOw7_9n_EkxJxWLkJf9-nFCVRrbfSLqSJBeE6OKNAu2VLLYrSFE7GkVXNCFVugoCDM2oVJogX75AgzWimrp75QRmLsXbvB-YvvRkQ8Gfb2RZnqCj9kiYg' + ) + .build() + ); + + useEffect(() => { + connection.db.player.onInsert(player => { + console.log(player); + }); + + setTimeout(() => { + console.log(Array.from(connection.db.player.iter())); + }, 5000); + }, [connection]); + + return ( +
+

Typescript SDK Test!

+

{connection.identity?.toHexString()}

+ + +
+ ); +} + +export default App; diff --git a/sdks/typescript/packages/test-app/src/index.css b/sdks/typescript/packages/test-app/src/index.css new file mode 100644 index 00000000000..6119ad9a8fa --- /dev/null +++ b/sdks/typescript/packages/test-app/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/sdks/typescript/packages/test-app/src/main.tsx b/sdks/typescript/packages/test-app/src/main.tsx new file mode 100644 index 00000000000..93db3799c0c --- /dev/null +++ b/sdks/typescript/packages/test-app/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App.tsx'; +import './index.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); diff --git a/sdks/typescript/packages/test-app/src/module_bindings/create_player_reducer.ts b/sdks/typescript/packages/test-app/src/module_bindings/create_player_reducer.ts new file mode 100644 index 00000000000..55285852c94 --- /dev/null +++ b/sdks/typescript/packages/test-app/src/module_bindings/create_player_reducer.ts @@ -0,0 +1,65 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; + +import { Point as __Point } from './point_type'; + +export type CreatePlayer = { + name: string; + location: __Point; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace CreatePlayer { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('name', AlgebraicType.createStringType()), + new ProductTypeElement('location', __Point.getTypeScriptAlgebraicType()), + ]); + } + + export function serialize(writer: BinaryWriter, value: CreatePlayer): void { + CreatePlayer.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): CreatePlayer { + return CreatePlayer.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/test-app/src/module_bindings/index.ts b/sdks/typescript/packages/test-app/src/module_bindings/index.ts new file mode 100644 index 00000000000..86fcf287d7b --- /dev/null +++ b/sdks/typescript/packages/test-app/src/module_bindings/index.ts @@ -0,0 +1,238 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; + +// Import and reexport all reducer arg types +import { CreatePlayer } from './create_player_reducer.ts'; +export { CreatePlayer }; + +// Import and reexport all table handle types +import { PlayerTableHandle } from './player_table.ts'; +export { PlayerTableHandle }; +import { UnindexedPlayerTableHandle } from './unindexed_player_table.ts'; +export { UnindexedPlayerTableHandle }; +import { UserTableHandle } from './user_table.ts'; +export { UserTableHandle }; + +// Import and reexport all types +import { Player } from './player_type.ts'; +export { Player }; +import { Point } from './point_type.ts'; +export { Point }; +import { UnindexedPlayer } from './unindexed_player_type.ts'; +export { UnindexedPlayer }; +import { User } from './user_type.ts'; +export { User }; + +const REMOTE_MODULE = { + tables: { + player: { + tableName: 'player', + rowType: Player.getTypeScriptAlgebraicType(), + primaryKey: 'ownerId', + primaryKeyInfo: { + colName: 'ownerId', + colType: + Player.getTypeScriptAlgebraicType().product.elements[0].algebraicType, + }, + }, + unindexed_player: { + tableName: 'unindexed_player', + rowType: UnindexedPlayer.getTypeScriptAlgebraicType(), + }, + user: { + tableName: 'user', + rowType: User.getTypeScriptAlgebraicType(), + primaryKey: 'identity', + primaryKeyInfo: { + colName: 'identity', + colType: + User.getTypeScriptAlgebraicType().product.elements[0].algebraicType, + }, + }, + }, + reducers: { + create_player: { + reducerName: 'create_player', + argsType: CreatePlayer.getTypeScriptAlgebraicType(), + }, + }, + versionInfo: { + cliVersion: '1.2.0', + }, + // Constructors which are used by the DbConnectionImpl to + // extract type information from the generated RemoteModule. + // + // NOTE: This is not strictly necessary for `eventContextConstructor` because + // all we do is build a TypeScript object which we could have done inside the + // SDK, but if in the future we wanted to create a class this would be + // necessary because classes have methods, so we'll keep it. + eventContextConstructor: (imp: DbConnectionImpl, event: Event) => { + return { + ...(imp as DbConnection), + event, + }; + }, + dbViewConstructor: (imp: DbConnectionImpl) => { + return new RemoteTables(imp); + }, + reducersConstructor: ( + imp: DbConnectionImpl, + setReducerFlags: SetReducerFlags + ) => { + return new RemoteReducers(imp, setReducerFlags); + }, + setReducerFlagsConstructor: () => { + return new SetReducerFlags(); + }, +}; + +// A type representing all the possible variants of a reducer. +export type Reducer = never | { name: 'CreatePlayer'; args: CreatePlayer }; + +export class RemoteReducers { + constructor( + private connection: DbConnectionImpl, + private setCallReducerFlags: SetReducerFlags + ) {} + + createPlayer(name: string, location: Point) { + const __args = { name, location }; + let __writer = new BinaryWriter(1024); + CreatePlayer.getTypeScriptAlgebraicType().serialize(__writer, __args); + let __argsBuffer = __writer.getBuffer(); + this.connection.callReducer( + 'create_player', + __argsBuffer, + this.setCallReducerFlags.createPlayerFlags + ); + } + + onCreatePlayer( + callback: (ctx: ReducerEventContext, name: string, location: Point) => void + ) { + this.connection.onReducer('create_player', callback); + } + + removeOnCreatePlayer( + callback: (ctx: ReducerEventContext, name: string, location: Point) => void + ) { + this.connection.offReducer('create_player', callback); + } +} + +export class SetReducerFlags { + createPlayerFlags: CallReducerFlags = 'FullUpdate'; + createPlayer(flags: CallReducerFlags) { + this.createPlayerFlags = flags; + } +} + +export class RemoteTables { + constructor(private connection: DbConnectionImpl) {} + + get player(): PlayerTableHandle { + return new PlayerTableHandle( + this.connection.clientCache.getOrCreateTable( + REMOTE_MODULE.tables.player + ) + ); + } + + get unindexedPlayer(): UnindexedPlayerTableHandle { + return new UnindexedPlayerTableHandle( + this.connection.clientCache.getOrCreateTable( + REMOTE_MODULE.tables.unindexed_player + ) + ); + } + + get user(): UserTableHandle { + return new UserTableHandle( + this.connection.clientCache.getOrCreateTable( + REMOTE_MODULE.tables.user + ) + ); + } +} + +export class SubscriptionBuilder extends SubscriptionBuilderImpl< + RemoteTables, + RemoteReducers, + SetReducerFlags +> {} + +export class DbConnection extends DbConnectionImpl< + RemoteTables, + RemoteReducers, + SetReducerFlags +> { + static builder = (): DbConnectionBuilder< + DbConnection, + ErrorContext, + SubscriptionEventContext + > => { + return new DbConnectionBuilder< + DbConnection, + ErrorContext, + SubscriptionEventContext + >(REMOTE_MODULE, (imp: DbConnectionImpl) => imp as DbConnection); + }; + subscriptionBuilder = (): SubscriptionBuilder => { + return new SubscriptionBuilder(this); + }; +} + +export type EventContext = EventContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags, + Reducer +>; +export type ReducerEventContext = ReducerEventContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags, + Reducer +>; +export type SubscriptionEventContext = SubscriptionEventContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags +>; +export type ErrorContext = ErrorContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags +>; diff --git a/sdks/typescript/packages/test-app/src/module_bindings/player_table.ts b/sdks/typescript/packages/test-app/src/module_bindings/player_table.ts new file mode 100644 index 00000000000..7eec606c5d5 --- /dev/null +++ b/sdks/typescript/packages/test-app/src/module_bindings/player_table.ts @@ -0,0 +1,120 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; +import { Player } from './player_type'; +import { Point as __Point } from './point_type'; + +import { + type EventContext, + type Reducer, + RemoteReducers, + RemoteTables, +} from '.'; + +/** + * Table handle for the table `player`. + * + * Obtain a handle from the [`player`] property on [`RemoteTables`], + * like `ctx.db.player`. + * + * Users are encouraged not to explicitly reference this type, + * but to directly chain method calls, + * like `ctx.db.player.on_insert(...)`. + */ +export class PlayerTableHandle { + tableCache: TableCache; + + constructor(tableCache: TableCache) { + this.tableCache = tableCache; + } + + count(): number { + return this.tableCache.count(); + } + + iter(): Iterable { + return this.tableCache.iter(); + } + /** + * Access to the `ownerId` unique index on the table `player`, + * which allows point queries on the field of the same name + * via the [`PlayerOwnerIdUnique.find`] method. + * + * Users are encouraged not to explicitly reference this type, + * but to directly chain method calls, + * like `ctx.db.player.ownerId().find(...)`. + * + * Get a handle on the `ownerId` unique index on the table `player`. + */ + ownerId = { + // Find the subscribed row whose `ownerId` column value is equal to `col_val`, + // if such a row is present in the client cache. + find: (col_val: string): Player | undefined => { + for (let row of this.tableCache.iter()) { + if (deepEqual(row.ownerId, col_val)) { + return row; + } + } + }, + }; + + onInsert = (cb: (ctx: EventContext, row: Player) => void) => { + return this.tableCache.onInsert(cb); + }; + + removeOnInsert = (cb: (ctx: EventContext, row: Player) => void) => { + return this.tableCache.removeOnInsert(cb); + }; + + onDelete = (cb: (ctx: EventContext, row: Player) => void) => { + return this.tableCache.onDelete(cb); + }; + + removeOnDelete = (cb: (ctx: EventContext, row: Player) => void) => { + return this.tableCache.removeOnDelete(cb); + }; + + // Updates are only defined for tables with primary keys. + onUpdate = ( + cb: (ctx: EventContext, oldRow: Player, newRow: Player) => void + ) => { + return this.tableCache.onUpdate(cb); + }; + + removeOnUpdate = ( + cb: (ctx: EventContext, onRow: Player, newRow: Player) => void + ) => { + return this.tableCache.removeOnUpdate(cb); + }; +} diff --git a/sdks/typescript/packages/test-app/src/module_bindings/player_type.ts b/sdks/typescript/packages/test-app/src/module_bindings/player_type.ts new file mode 100644 index 00000000000..884e63551ba --- /dev/null +++ b/sdks/typescript/packages/test-app/src/module_bindings/player_type.ts @@ -0,0 +1,66 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; +import { Point as __Point } from './point_type'; + +export type Player = { + ownerId: string; + name: string; + location: __Point; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace Player { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('ownerId', AlgebraicType.createStringType()), + new ProductTypeElement('name', AlgebraicType.createStringType()), + new ProductTypeElement('location', __Point.getTypeScriptAlgebraicType()), + ]); + } + + export function serialize(writer: BinaryWriter, value: Player): void { + Player.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): Player { + return Player.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/test-app/src/module_bindings/point_type.ts b/sdks/typescript/packages/test-app/src/module_bindings/point_type.ts new file mode 100644 index 00000000000..c74f56ad623 --- /dev/null +++ b/sdks/typescript/packages/test-app/src/module_bindings/point_type.ts @@ -0,0 +1,62 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; +export type Point = { + x: number; + y: number; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace Point { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('x', AlgebraicType.createU16Type()), + new ProductTypeElement('y', AlgebraicType.createU16Type()), + ]); + } + + export function serialize(writer: BinaryWriter, value: Point): void { + Point.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): Point { + return Point.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/test-app/src/module_bindings/unindexed_player_table.ts b/sdks/typescript/packages/test-app/src/module_bindings/unindexed_player_table.ts new file mode 100644 index 00000000000..a0b3c754db9 --- /dev/null +++ b/sdks/typescript/packages/test-app/src/module_bindings/unindexed_player_table.ts @@ -0,0 +1,85 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; +import { UnindexedPlayer } from './unindexed_player_type'; +import { Point as __Point } from './point_type'; + +import { + type EventContext, + type Reducer, + RemoteReducers, + RemoteTables, +} from '.'; + +/** + * Table handle for the table `unindexed_player`. + * + * Obtain a handle from the [`unindexedPlayer`] property on [`RemoteTables`], + * like `ctx.db.unindexedPlayer`. + * + * Users are encouraged not to explicitly reference this type, + * but to directly chain method calls, + * like `ctx.db.unindexedPlayer.on_insert(...)`. + */ +export class UnindexedPlayerTableHandle { + tableCache: TableCache; + + constructor(tableCache: TableCache) { + this.tableCache = tableCache; + } + + count(): number { + return this.tableCache.count(); + } + + iter(): Iterable { + return this.tableCache.iter(); + } + + onInsert = (cb: (ctx: EventContext, row: UnindexedPlayer) => void) => { + return this.tableCache.onInsert(cb); + }; + + removeOnInsert = (cb: (ctx: EventContext, row: UnindexedPlayer) => void) => { + return this.tableCache.removeOnInsert(cb); + }; + + onDelete = (cb: (ctx: EventContext, row: UnindexedPlayer) => void) => { + return this.tableCache.onDelete(cb); + }; + + removeOnDelete = (cb: (ctx: EventContext, row: UnindexedPlayer) => void) => { + return this.tableCache.removeOnDelete(cb); + }; +} diff --git a/sdks/typescript/packages/test-app/src/module_bindings/unindexed_player_type.ts b/sdks/typescript/packages/test-app/src/module_bindings/unindexed_player_type.ts new file mode 100644 index 00000000000..82f25307017 --- /dev/null +++ b/sdks/typescript/packages/test-app/src/module_bindings/unindexed_player_type.ts @@ -0,0 +1,69 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; +import { Point as __Point } from './point_type'; + +export type UnindexedPlayer = { + ownerId: string; + name: string; + location: __Point; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace UnindexedPlayer { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('ownerId', AlgebraicType.createStringType()), + new ProductTypeElement('name', AlgebraicType.createStringType()), + new ProductTypeElement('location', __Point.getTypeScriptAlgebraicType()), + ]); + } + + export function serialize( + writer: BinaryWriter, + value: UnindexedPlayer + ): void { + UnindexedPlayer.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): UnindexedPlayer { + return UnindexedPlayer.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/test-app/src/module_bindings/user_table.ts b/sdks/typescript/packages/test-app/src/module_bindings/user_table.ts new file mode 100644 index 00000000000..605a14bc304 --- /dev/null +++ b/sdks/typescript/packages/test-app/src/module_bindings/user_table.ts @@ -0,0 +1,116 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; +import { User } from './user_type'; +import { + type EventContext, + type Reducer, + RemoteReducers, + RemoteTables, +} from '.'; + +/** + * Table handle for the table `user`. + * + * Obtain a handle from the [`user`] property on [`RemoteTables`], + * like `ctx.db.user`. + * + * Users are encouraged not to explicitly reference this type, + * but to directly chain method calls, + * like `ctx.db.user.on_insert(...)`. + */ +export class UserTableHandle { + tableCache: TableCache; + + constructor(tableCache: TableCache) { + this.tableCache = tableCache; + } + + count(): number { + return this.tableCache.count(); + } + + iter(): Iterable { + return this.tableCache.iter(); + } + /** + * Access to the `identity` unique index on the table `user`, + * which allows point queries on the field of the same name + * via the [`UserIdentityUnique.find`] method. + * + * Users are encouraged not to explicitly reference this type, + * but to directly chain method calls, + * like `ctx.db.user.identity().find(...)`. + * + * Get a handle on the `identity` unique index on the table `user`. + */ + identity = { + // Find the subscribed row whose `identity` column value is equal to `col_val`, + // if such a row is present in the client cache. + find: (col_val: Identity): User | undefined => { + for (let row of this.tableCache.iter()) { + if (deepEqual(row.identity, col_val)) { + return row; + } + } + }, + }; + + onInsert = (cb: (ctx: EventContext, row: User) => void) => { + return this.tableCache.onInsert(cb); + }; + + removeOnInsert = (cb: (ctx: EventContext, row: User) => void) => { + return this.tableCache.removeOnInsert(cb); + }; + + onDelete = (cb: (ctx: EventContext, row: User) => void) => { + return this.tableCache.onDelete(cb); + }; + + removeOnDelete = (cb: (ctx: EventContext, row: User) => void) => { + return this.tableCache.removeOnDelete(cb); + }; + + // Updates are only defined for tables with primary keys. + onUpdate = (cb: (ctx: EventContext, oldRow: User, newRow: User) => void) => { + return this.tableCache.onUpdate(cb); + }; + + removeOnUpdate = ( + cb: (ctx: EventContext, onRow: User, newRow: User) => void + ) => { + return this.tableCache.removeOnUpdate(cb); + }; +} diff --git a/sdks/typescript/packages/test-app/src/module_bindings/user_type.ts b/sdks/typescript/packages/test-app/src/module_bindings/user_type.ts new file mode 100644 index 00000000000..4a3943a26bf --- /dev/null +++ b/sdks/typescript/packages/test-app/src/module_bindings/user_type.ts @@ -0,0 +1,62 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.2.0 (commit fb41e50eb73573b70eea532aeb6158eaac06fae0). + +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +import { + AlgebraicType, + AlgebraicValue, + BinaryReader, + BinaryWriter, + ConnectionId, + DbConnectionBuilder, + DbConnectionImpl, + Identity, + ProductType, + ProductTypeElement, + SubscriptionBuilderImpl, + SumType, + SumTypeVariant, + TableCache, + TimeDuration, + Timestamp, + deepEqual, + type CallReducerFlags, + type DbContext, + type ErrorContextInterface, + type Event, + type EventContextInterface, + type ReducerEventContextInterface, + type SubscriptionEventContextInterface, +} from '@clockworklabs/spacetimedb-sdk'; +export type User = { + identity: Identity; + username: string; +}; + +/** + * A namespace for generated helper functions. + */ +export namespace User { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + export function getTypeScriptAlgebraicType(): AlgebraicType { + return AlgebraicType.createProductType([ + new ProductTypeElement('identity', AlgebraicType.createIdentityType()), + new ProductTypeElement('username', AlgebraicType.createStringType()), + ]); + } + + export function serialize(writer: BinaryWriter, value: User): void { + User.getTypeScriptAlgebraicType().serialize(writer, value); + } + + export function deserialize(reader: BinaryReader): User { + return User.getTypeScriptAlgebraicType().deserialize(reader); + } +} diff --git a/sdks/typescript/packages/test-app/src/vite-env.d.ts b/sdks/typescript/packages/test-app/src/vite-env.d.ts new file mode 100644 index 00000000000..11f02fe2a00 --- /dev/null +++ b/sdks/typescript/packages/test-app/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/sdks/typescript/packages/test-app/tsconfig.app.json b/sdks/typescript/packages/test-app/tsconfig.app.json new file mode 100644 index 00000000000..d739292ae01 --- /dev/null +++ b/sdks/typescript/packages/test-app/tsconfig.app.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/sdks/typescript/packages/test-app/tsconfig.json b/sdks/typescript/packages/test-app/tsconfig.json new file mode 100644 index 00000000000..ea9d0cd8255 --- /dev/null +++ b/sdks/typescript/packages/test-app/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.node.json" + } + ] +} diff --git a/sdks/typescript/packages/test-app/tsconfig.node.json b/sdks/typescript/packages/test-app/tsconfig.node.json new file mode 100644 index 00000000000..3afdd6e3843 --- /dev/null +++ b/sdks/typescript/packages/test-app/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true, + "noEmit": true + }, + "include": ["vite.config.ts"] +} diff --git a/sdks/typescript/packages/test-app/vite.config.ts b/sdks/typescript/packages/test-app/vite.config.ts new file mode 100644 index 00000000000..627a3196243 --- /dev/null +++ b/sdks/typescript/packages/test-app/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}); diff --git a/sdks/typescript/pnpm-lock.yaml b/sdks/typescript/pnpm-lock.yaml new file mode 100644 index 00000000000..fdf976654bf --- /dev/null +++ b/sdks/typescript/pnpm-lock.yaml @@ -0,0 +1,5495 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@changesets/changelog-github': + specifier: ^0.5.0 + version: 0.5.0 + '@changesets/cli': + specifier: ^2.27.7 + version: 2.27.9 + brotli-size-cli: + specifier: ^1.0.0 + version: 1.0.0 + prettier: + specifier: ^3.3.3 + version: 3.3.3 + terser: + specifier: ^5.31.2 + version: 5.34.1 + tsup: + specifier: ^8.1.0 + version: 8.3.0(postcss@8.5.1)(tsx@4.19.1)(typescript@5.6.2) + tsx: + specifier: ^4.17.0 + version: 4.19.1 + typescript: + specifier: ^5.5.3 + version: 5.6.2 + vitest: + specifier: ^2.0.3 + version: 2.1.2(@types/node@22.15.0)(jsdom@26.0.0)(terser@5.34.1) + + examples/quickstart-chat: + dependencies: + '@clockworklabs/spacetimedb-sdk': + specifier: workspace:* + version: link:../../packages/sdk + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@eslint/js': + specifier: ^9.17.0 + version: 9.18.0 + '@testing-library/jest-dom': + specifier: ^6.6.3 + version: 6.6.3 + '@testing-library/react': + specifier: ^16.2.0 + version: 16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.0) + '@types/jest': + specifier: ^29.5.14 + version: 29.5.14 + '@types/react': + specifier: ^18.3.18 + version: 18.3.18 + '@types/react-dom': + specifier: ^18.3.5 + version: 18.3.5(@types/react@18.3.18) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.3.4(vite@6.0.11(@types/node@22.15.0)(terser@5.34.1)(tsx@4.19.1)) + eslint: + specifier: ^9.17.0 + version: 9.18.0 + eslint-plugin-react-hooks: + specifier: ^5.0.0 + version: 5.1.0(eslint@9.18.0) + eslint-plugin-react-refresh: + specifier: ^0.4.16 + version: 0.4.18(eslint@9.18.0) + globals: + specifier: ^15.14.0 + version: 15.14.0 + jsdom: + specifier: ^26.0.0 + version: 26.0.0 + typescript: + specifier: ~5.6.2 + version: 5.6.2 + typescript-eslint: + specifier: ^8.18.2 + version: 8.21.0(eslint@9.18.0)(typescript@5.6.2) + vite: + specifier: ^6.0.5 + version: 6.0.11(@types/node@22.15.0)(terser@5.34.1)(tsx@4.19.1) + + examples/repro-07032025: + dependencies: + '@clockworklabs/spacetimedb-sdk': + specifier: workspace:* + version: link:../../packages/sdk + devDependencies: + '@types/node': + specifier: ^22.13.9 + version: 22.15.0 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@22.15.0)(typescript@5.8.3) + typescript: + specifier: ^5.8.2 + version: 5.8.3 + + examples/repro2: + dependencies: + '@clockworklabs/spacetimedb-sdk': + specifier: workspace:* + version: link:../../packages/sdk + devDependencies: + '@eslint/js': + specifier: ^9.17.0 + version: 9.18.0 + '@testing-library/jest-dom': + specifier: ^6.6.3 + version: 6.6.3 + '@testing-library/react': + specifier: ^16.2.0 + version: 16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.0) + '@types/jest': + specifier: ^29.5.14 + version: 29.5.14 + '@types/react': + specifier: ^18.3.18 + version: 18.3.18 + '@types/react-dom': + specifier: ^18.3.5 + version: 18.3.5(@types/react@18.3.18) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.3.4(vite@6.0.11(@types/node@22.15.0)(terser@5.34.1)(tsx@4.19.1)) + eslint: + specifier: ^9.17.0 + version: 9.18.0 + eslint-plugin-react-hooks: + specifier: ^5.0.0 + version: 5.1.0(eslint@9.18.0) + eslint-plugin-react-refresh: + specifier: ^0.4.16 + version: 0.4.18(eslint@9.18.0) + globals: + specifier: ^15.14.0 + version: 15.14.0 + jsdom: + specifier: ^26.0.0 + version: 26.0.0 + typescript: + specifier: ~5.6.2 + version: 5.6.2 + typescript-eslint: + specifier: ^8.18.2 + version: 8.21.0(eslint@9.18.0)(typescript@5.6.2) + vite: + specifier: ^6.0.5 + version: 6.0.11(@types/node@22.15.0)(terser@5.34.1)(tsx@4.19.1) + + packages/sdk: + dependencies: + base64-js: + specifier: ^1.5.1 + version: 1.5.1 + devDependencies: + '@clockworklabs/test-app': + specifier: file:../test-app + version: file:packages/test-app + tsup: + specifier: ^8.1.0 + version: 8.3.0(postcss@8.5.1)(tsx@4.19.1)(typescript@5.8.3) + undici: + specifier: ^6.19.2 + version: 6.19.8 + + packages/test-app: + dependencies: + '@clockworklabs/spacetimedb-sdk': + specifier: workspace:* + version: link:../sdk + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@types/react': + specifier: ^18.3.3 + version: 18.3.11 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.0 + '@vitejs/plugin-react': + specifier: ^4.3.1 + version: 4.3.2(vite@5.4.8(@types/node@22.15.0)(terser@5.34.1)) + typescript: + specifier: ^5.2.2 + version: 5.6.2 + vite: + specifier: ^5.3.4 + version: 5.4.8(@types/node@22.15.0)(terser@5.34.1) + +packages: + + '@adobe/css-tools@4.4.1': + resolution: {integrity: sha512-12WGKBQzjUAI4ayyF4IAtfw2QR/IDoqk6jTddXDhtYTJF9ASmoE1zst7cVtP0aL/F1jUJL5r+JxKXKEgHNbEUQ==} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@asamuzakjp/css-color@2.8.3': + resolution: {integrity: sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==} + + '@babel/code-frame@7.25.7': + resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==} + engines: {node: '>=6.9.0'} + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.25.7': + resolution: {integrity: sha512-9ickoLz+hcXCeh7jrcin+/SLWm+GkxE2kTvoYyp38p4WkdFXfQJxDFGWp/YHjiKLPx06z2A7W8XKuqbReXDzsw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.26.5': + resolution: {integrity: sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.25.7': + resolution: {integrity: sha512-yJ474Zv3cwiSOO9nXJuqzvwEeM+chDuQ8GJirw+pZ91sCGCyOZ3dJkVE09fTV0VEVzXyLWhh3G/AolYTPX7Mow==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.0': + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.25.7': + resolution: {integrity: sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.26.5': + resolution: {integrity: sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.25.7': + resolution: {integrity: sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.26.5': + resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.7': + resolution: {integrity: sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.25.7': + resolution: {integrity: sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.25.7': + resolution: {integrity: sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.26.5': + resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-simple-access@7.25.7': + resolution: {integrity: sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.7': + resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.7': + resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.7': + resolution: {integrity: sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.25.7': + resolution: {integrity: sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.26.0': + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.25.7': + resolution: {integrity: sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.25.7': + resolution: {integrity: sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/parser@7.26.5': + resolution: {integrity: sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.25.7': + resolution: {integrity: sha512-JD9MUnLbPL0WdVK8AWC7F7tTG2OS6u/AKKnsK+NdRhUiVdnzyR1S3kKQCaRLOiaULvUiqK6Z4JQE635VgtCFeg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.25.9': + resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.25.7': + resolution: {integrity: sha512-S/JXG/KrbIY06iyJPKfxr0qRxnhNOdkNXYBl/rmwgDd72cQLH9tEGkDm/yJPGvcSIUoikzfjMios9i+xT/uv9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.25.9': + resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.25.7': + resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.25.7': + resolution: {integrity: sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.25.9': + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.25.7': + resolution: {integrity: sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.26.5': + resolution: {integrity: sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.25.7': + resolution: {integrity: sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.26.5': + resolution: {integrity: sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==} + engines: {node: '>=6.9.0'} + + '@changesets/apply-release-plan@7.0.5': + resolution: {integrity: sha512-1cWCk+ZshEkSVEZrm2fSj1Gz8sYvxgUL4Q78+1ZZqeqfuevPTPk033/yUZ3df8BKMohkqqHfzj0HOOrG0KtXTw==} + + '@changesets/assemble-release-plan@6.0.4': + resolution: {integrity: sha512-nqICnvmrwWj4w2x0fOhVj2QEGdlUuwVAwESrUo5HLzWMI1rE5SWfsr9ln+rDqWB6RQ2ZyaMZHUcU7/IRaUJS+Q==} + + '@changesets/changelog-git@0.2.0': + resolution: {integrity: sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==} + + '@changesets/changelog-github@0.5.0': + resolution: {integrity: sha512-zoeq2LJJVcPJcIotHRJEEA2qCqX0AQIeFE+L21L8sRLPVqDhSXY8ZWAt2sohtBpFZkBwu+LUwMSKRr2lMy3LJA==} + + '@changesets/cli@2.27.9': + resolution: {integrity: sha512-q42a/ZbDnxPpCb5Wkm6tMVIxgeI9C/bexntzTeCFBrQEdpisQqk8kCHllYZMDjYtEc1ZzumbMJAG8H0Z4rdvjg==} + hasBin: true + + '@changesets/config@3.0.3': + resolution: {integrity: sha512-vqgQZMyIcuIpw9nqFIpTSNyc/wgm/Lu1zKN5vECy74u95Qx/Wa9g27HdgO4NkVAaq+BGA8wUc/qvbvVNs93n6A==} + + '@changesets/errors@0.2.0': + resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} + + '@changesets/get-dependents-graph@2.1.2': + resolution: {integrity: sha512-sgcHRkiBY9i4zWYBwlVyAjEM9sAzs4wYVwJUdnbDLnVG3QwAaia1Mk5P8M7kraTOZN+vBET7n8KyB0YXCbFRLQ==} + + '@changesets/get-github-info@0.6.0': + resolution: {integrity: sha512-v/TSnFVXI8vzX9/w3DU2Ol+UlTZcu3m0kXTjTT4KlAdwSvwutcByYwyYn9hwerPWfPkT2JfpoX0KgvCEi8Q/SA==} + + '@changesets/get-release-plan@4.0.4': + resolution: {integrity: sha512-SicG/S67JmPTrdcc9Vpu0wSQt7IiuN0dc8iR5VScnnTVPfIaLvKmEGRvIaF0kcn8u5ZqLbormZNTO77bCEvyWw==} + + '@changesets/get-version-range-type@0.4.0': + resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} + + '@changesets/git@3.0.1': + resolution: {integrity: sha512-pdgHcYBLCPcLd82aRcuO0kxCDbw/yISlOtkmwmE8Odo1L6hSiZrBOsRl84eYG7DRCab/iHnOkWqExqc4wxk2LQ==} + + '@changesets/logger@0.1.1': + resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} + + '@changesets/parse@0.4.0': + resolution: {integrity: sha512-TS/9KG2CdGXS27S+QxbZXgr8uPsP4yNJYb4BC2/NeFUj80Rni3TeD2qwWmabymxmrLo7JEsytXH1FbpKTbvivw==} + + '@changesets/pre@2.0.1': + resolution: {integrity: sha512-vvBJ/If4jKM4tPz9JdY2kGOgWmCowUYOi5Ycv8dyLnEE8FgpYYUo1mgJZxcdtGGP3aG8rAQulGLyyXGSLkIMTQ==} + + '@changesets/read@0.6.1': + resolution: {integrity: sha512-jYMbyXQk3nwP25nRzQQGa1nKLY0KfoOV7VLgwucI0bUO8t8ZLCr6LZmgjXsiKuRDc+5A6doKPr9w2d+FEJ55zQ==} + + '@changesets/should-skip-package@0.1.1': + resolution: {integrity: sha512-H9LjLbF6mMHLtJIc/eHR9Na+MifJ3VxtgP/Y+XLn4BF7tDTEN1HNYtH6QMcjP1uxp9sjaFYmW8xqloaCi/ckTg==} + + '@changesets/types@4.1.0': + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + + '@changesets/types@6.0.0': + resolution: {integrity: sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==} + + '@changesets/write@0.3.2': + resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} + + '@clockworklabs/test-app@file:packages/test-app': + resolution: {directory: packages/test-app, type: directory} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@csstools/color-helpers@5.0.1': + resolution: {integrity: sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.1': + resolution: {integrity: sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-color-parser@3.0.7': + resolution: {integrity: sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-parser-algorithms@3.0.4': + resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-tokenizer@3.0.3': + resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} + engines: {node: '>=18'} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.19.1': + resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.10.0': + resolution: {integrity: sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.2.0': + resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.18.0': + resolution: {integrity: sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.5': + resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.5': + resolution: {integrity: sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.1': + resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + engines: {node: '>=18.18'} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.6': + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@manypkg/find-root@1.1.0': + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + + '@manypkg/get-packages@1.1.3': + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rollup/rollup-android-arm-eabi@4.24.0': + resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.24.0': + resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.24.0': + resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.24.0': + resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.24.0': + resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.24.0': + resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.24.0': + resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.24.0': + resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.24.0': + resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.24.0': + resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.24.0': + resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.24.0': + resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.24.0': + resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.24.0': + resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==} + cpu: [x64] + os: [win32] + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@testing-library/dom@10.4.0': + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.6.3': + resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.2.0': + resolution: {integrity: sha512-2cSskAvA1QNtKc8Y9VJQRv0tm3hLVgxRGDB+KYhIaPQJ1I+RHbhIXcM+zClKXzMes/wshsMVzf4B9vS4IZpqDQ==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + + '@types/node@22.15.0': + resolution: {integrity: sha512-99S8dWD2DkeE6PBaEDw+In3aar7hdoBvjyJMR6vaKBTzpvR0P00ClzJMOoVrj9D2+Sy/YCwACYHnBTpMhg1UCA==} + + '@types/prop-types@15.7.13': + resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} + + '@types/react-dom@18.3.0': + resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + + '@types/react-dom@18.3.5': + resolution: {integrity: sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.11': + resolution: {integrity: sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==} + + '@types/react@18.3.18': + resolution: {integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + + '@typescript-eslint/eslint-plugin@8.21.0': + resolution: {integrity: sha512-eTH+UOR4I7WbdQnG4Z48ebIA6Bgi7WO8HvFEneeYBxG8qCOYgTOFPSg6ek9ITIDvGjDQzWHcoWHCDO2biByNzA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/parser@8.21.0': + resolution: {integrity: sha512-Wy+/sdEH9kI3w9civgACwabHbKl+qIOu0uFZ9IMKzX3Jpv9og0ZBJrZExGrPpFAY7rWsXuxs5e7CPPP17A4eYA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/scope-manager@8.21.0': + resolution: {integrity: sha512-G3IBKz0/0IPfdeGRMbp+4rbjfSSdnGkXsM/pFZA8zM9t9klXDnB/YnKOBQ0GoPmoROa4bCq2NeHgJa5ydsQ4mA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.21.0': + resolution: {integrity: sha512-95OsL6J2BtzoBxHicoXHxgk3z+9P3BEcQTpBKriqiYzLKnM2DeSqs+sndMKdamU8FosiadQFT3D+BSL9EKnAJQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/types@8.21.0': + resolution: {integrity: sha512-PAL6LUuQwotLW2a8VsySDBwYMm129vFm4tMVlylzdoTybTHaAi0oBp7Ac6LhSrHHOdLM3efH+nAR6hAWoMF89A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.21.0': + resolution: {integrity: sha512-x+aeKh/AjAArSauz0GiQZsjT8ciadNMHdkUSwBB9Z6PrKc/4knM4g3UfHml6oDJmKC88a6//cdxnO/+P2LkMcg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/utils@8.21.0': + resolution: {integrity: sha512-xcXBfcq0Kaxgj7dwejMbFyq7IOHgpNMtVuDveK7w3ZGwG9owKzhALVwKpTF2yrZmEwl9SWdetf3fxNzJQaVuxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/visitor-keys@8.21.0': + resolution: {integrity: sha512-BkLMNpdV6prozk8LlyK/SOoWLmUFi+ZD+pcqti9ILCbVvHGk1ui1g4jJOc2WDLaeExz2qWwojxlPce5PljcT3w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react@4.3.2': + resolution: {integrity: sha512-hieu+o05v4glEBucTcKMK3dlES0OeJlD9YVOAPraVMOInBCwzumaIFiUjr4bHK7NPgnAHgiskUoceKercrN8vg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 + + '@vitejs/plugin-react@4.3.4': + resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 + + '@vitest/expect@2.1.2': + resolution: {integrity: sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==} + + '@vitest/mocker@2.1.2': + resolution: {integrity: sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==} + peerDependencies: + '@vitest/spy': 2.1.2 + msw: ^2.3.5 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.2': + resolution: {integrity: sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==} + + '@vitest/runner@2.1.2': + resolution: {integrity: sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==} + + '@vitest/snapshot@2.1.2': + resolution: {integrity: sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==} + + '@vitest/spy@2.1.2': + resolution: {integrity: sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==} + + '@vitest/utils@2.1.2': + resolution: {integrity: sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + engines: {node: '>=0.4.0'} + hasBin: true + + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + better-path-resolve@1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + brotli-size-cli@1.0.0: + resolution: {integrity: sha512-eX+2WEZDvjWg045PxVN3eDYWCqyNuKmunjCZX9Ps0wohWMxGfiMP+efWVAuU/2kL81A4qfmHOAWWKFRV56XkCw==} + engines: {node: '>=13'} + hasBin: true + + brotli-size@4.0.0: + resolution: {integrity: sha512-uA9fOtlTRC0iqKfzff1W34DXUA3GyVqbUaeo3Rw3d4gd1eavKVCETXrn3NzO74W+UVkG3UHu8WxUi+XvKI/huA==} + engines: {node: '>= 10.16.0'} + + browserslist@4.24.0: + resolution: {integrity: sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + bundle-require@5.0.0: + resolution: {integrity: sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001667: + resolution: {integrity: sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==} + + chai@5.1.1: + resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + engines: {node: '>=12'} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + consola@3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} + engines: {node: ^14.18.0 || >=16.10.0} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-spawn@5.1.0: + resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + cssstyle@4.2.1: + resolution: {integrity: sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==} + engines: {node: '>=18'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + + dataloader@1.4.0: + resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} + + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.5.0: + resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + dotenv@8.6.0: + resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} + engines: {node: '>=10'} + + duplexer@0.1.1: + resolution: {integrity: sha512-sxNZ+ljy+RA1maXoUReeqBBpBC6RLKmg5ewzV+x+mSETmWNoKdZN6vcQjpFROemza23hGFskJtFNoUWUaQ+R4Q==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.33: + resolution: {integrity: sha512-+cYTcFB1QqD4j4LegwLfpCNxifb6dDFUAwk6RsLusCwIaZI6or2f+q8rs5tTB2YC53HhOlIbEaqHMAAC8IOIwA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@5.1.0: + resolution: {integrity: sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.18: + resolution: {integrity: sha512-IRGEoFn3OKalm3hjfolEWGqoF/jPqeEYFp+C8B0WMzwGwBMvlRDQd06kghDhF0C61uJ6WfSDhEZE/sAQjduKgw==} + peerDependencies: + eslint: '>=8.40' + + eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.18.0: + resolution: {integrity: sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + extendable-error@0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fdir@6.4.0: + resolution: {integrity: sha512-3oB133prH1o4j/L5lLW7uOCF1PlD+/It2L0eL/iAqWMB91RBbqTewABqxhj0ibBd90EEmWZq7ntIWzVaWcXTGQ==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.2: + resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} + + fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-tsconfig@4.8.1: + resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.14.0: + resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==} + engines: {node: '>=18'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + human-id@1.0.2: + resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-subdir@1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsdom@26.0.0: + resolution: {integrity: sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nwsapi@2.2.16: + resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + outdent@0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + + p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + package-manager-detector@0.2.1: + resolution: {integrity: sha512-/hVW2fZvAdEas+wyKh0SnlZ2mx0NIa1+j11YaQkogEJkcMErbwchHCuo8z7lEtajZJQZ6rgZNVTWMVVd71Bjng==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + + picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.1: + resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + + pretty-bytes@5.6.0: + resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} + engines: {node: '>=6'} + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + pseudomap@1.0.2: + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-yaml-file@1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.24.0: + resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + + spawndamnit@2.0.0: + resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + term-size@2.2.1: + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} + engines: {node: '>=8'} + + terser@5.34.1: + resolution: {integrity: sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==} + engines: {node: '>=10'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.0: + resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} + + tinyglobby@0.2.9: + resolution: {integrity: sha512-8or1+BGEdk1Zkkw2ii16qSS7uVrQJPre5A9o/XkWPATkk23FZh/15BKFxPnlTy6vkljZxLqYCzzBMj30ZrSvjw==} + engines: {node: '>=12.0.0'} + + tinypool@1.0.1: + resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + tldts-core@6.1.75: + resolution: {integrity: sha512-AOvV5YYIAFFBfransBzSTyztkc3IMfz5Eq3YluaRiEu55nn43Fzaufx70UqEKYr8BoLCach4q8g/bg6e5+/aFw==} + + tldts@6.1.75: + resolution: {integrity: sha512-+lFzEXhpl7JXgWYaXcB6DqTYXbUArvrWAE/5ioq/X3CdWLbDjpPP4XTrQBmEJ91y3xbe4Fkw7Lxv4P3GWeJaNg==} + hasBin: true + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + + to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tough-cookie@5.1.0: + resolution: {integrity: sha512-rvZUv+7MoBYTiDmFPBrhL7Ujx9Sk+q9wwm22x8c8T5IJaR+Wsyc7TNxbVxo84kZoRJZZMazowFLqpankBEQrGg==} + engines: {node: '>=16'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + + tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-api-utils@2.0.0: + resolution: {integrity: sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tsup@8.3.0: + resolution: {integrity: sha512-ALscEeyS03IomcuNdFdc0YWGVIkwH1Ws7nfTbAPuoILvEV2hpGQAY72LIOjglGo4ShWpZfpBqP/jpQVCzqYQag==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + tsx@4.19.1: + resolution: {integrity: sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.21.0: + resolution: {integrity: sha512-txEKYY4XMKwPXxNkN8+AxAdX6iIJAPiJbHE/FpQccs/sxw8Lf26kqwC3cn0xkHlW8kEbLhkhCsjWuMveaY9Rxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + typescript@5.6.2: + resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} + engines: {node: '>=14.17'} + hasBin: true + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + undici@6.19.8: + resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==} + engines: {node: '>=18.17'} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + vite-node@2.1.2: + resolution: {integrity: sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.8: + resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vite@6.0.11: + resolution: {integrity: sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@2.1.2: + resolution: {integrity: sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.2 + '@vitest/ui': 2.1.2 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.1.0: + resolution: {integrity: sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==} + engines: {node: '>=18'} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + yallist@2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@adobe/css-tools@4.4.1': {} + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@asamuzakjp/css-color@2.8.3': + dependencies: + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + lru-cache: 10.4.3 + + '@babel/code-frame@7.25.7': + dependencies: + '@babel/highlight': 7.25.7 + picocolors: 1.1.0 + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.25.7': {} + + '@babel/compat-data@7.26.5': {} + + '@babel/core@7.25.7': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.25.7 + '@babel/generator': 7.25.7 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.7) + '@babel/helpers': 7.25.7 + '@babel/parser': 7.25.7 + '@babel/template': 7.25.7 + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.7 + convert-source-map: 2.0.0 + debug: 4.3.7 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/core@7.26.0': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.5 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helpers': 7.26.0 + '@babel/parser': 7.26.5 + '@babel/template': 7.25.9 + '@babel/traverse': 7.26.5 + '@babel/types': 7.26.5 + convert-source-map: 2.0.0 + debug: 4.3.7 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.25.7': + dependencies: + '@babel/types': 7.25.7 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 + + '@babel/generator@7.26.5': + dependencies: + '@babel/parser': 7.26.5 + '@babel/types': 7.26.5 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 + + '@babel/helper-compilation-targets@7.25.7': + dependencies: + '@babel/compat-data': 7.25.7 + '@babel/helper-validator-option': 7.25.7 + browserslist: 4.24.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-compilation-targets@7.26.5': + dependencies: + '@babel/compat-data': 7.26.5 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-module-imports@7.25.7': + dependencies: + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.26.5 + '@babel/types': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.25.7(@babel/core@7.25.7)': + dependencies: + '@babel/core': 7.25.7 + '@babel/helper-module-imports': 7.25.7 + '@babel/helper-simple-access': 7.25.7 + '@babel/helper-validator-identifier': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.25.7': {} + + '@babel/helper-plugin-utils@7.26.5': {} + + '@babel/helper-simple-access@7.25.7': + dependencies: + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.25.7': {} + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.7': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.7': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helpers@7.25.7': + dependencies: + '@babel/template': 7.25.7 + '@babel/types': 7.25.7 + + '@babel/helpers@7.26.0': + dependencies: + '@babel/template': 7.25.9 + '@babel/types': 7.26.5 + + '@babel/highlight@7.25.7': + dependencies: + '@babel/helper-validator-identifier': 7.25.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.1.0 + + '@babel/parser@7.25.7': + dependencies: + '@babel/types': 7.25.7 + + '@babel/parser@7.26.5': + dependencies: + '@babel/types': 7.26.5 + + '@babel/plugin-transform-react-jsx-self@7.25.7(@babel/core@7.25.7)': + dependencies: + '@babel/core': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-react-jsx-source@7.25.7(@babel/core@7.25.7)': + dependencies: + '@babel/core': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/runtime@7.25.7': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/template@7.25.7': + dependencies: + '@babel/code-frame': 7.25.7 + '@babel/parser': 7.25.7 + '@babel/types': 7.25.7 + + '@babel/template@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.5 + '@babel/types': 7.26.5 + + '@babel/traverse@7.25.7': + dependencies: + '@babel/code-frame': 7.25.7 + '@babel/generator': 7.25.7 + '@babel/parser': 7.25.7 + '@babel/template': 7.25.7 + '@babel/types': 7.25.7 + debug: 4.3.7 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/traverse@7.26.5': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.5 + '@babel/parser': 7.26.5 + '@babel/template': 7.25.9 + '@babel/types': 7.26.5 + debug: 4.3.7 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.25.7': + dependencies: + '@babel/helper-string-parser': 7.25.7 + '@babel/helper-validator-identifier': 7.25.7 + to-fast-properties: 2.0.0 + + '@babel/types@7.26.5': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@changesets/apply-release-plan@7.0.5': + dependencies: + '@changesets/config': 3.0.3 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.1 + '@changesets/should-skip-package': 0.1.1 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.8.8 + resolve-from: 5.0.0 + semver: 7.6.3 + + '@changesets/assemble-release-plan@6.0.4': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/should-skip-package': 0.1.1 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + semver: 7.6.3 + + '@changesets/changelog-git@0.2.0': + dependencies: + '@changesets/types': 6.0.0 + + '@changesets/changelog-github@0.5.0': + dependencies: + '@changesets/get-github-info': 0.6.0 + '@changesets/types': 6.0.0 + dotenv: 8.6.0 + transitivePeerDependencies: + - encoding + + '@changesets/cli@2.27.9': + dependencies: + '@changesets/apply-release-plan': 7.0.5 + '@changesets/assemble-release-plan': 6.0.4 + '@changesets/changelog-git': 0.2.0 + '@changesets/config': 3.0.3 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/get-release-plan': 4.0.4 + '@changesets/git': 3.0.1 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.1 + '@changesets/read': 0.6.1 + '@changesets/should-skip-package': 0.1.1 + '@changesets/types': 6.0.0 + '@changesets/write': 0.3.2 + '@manypkg/get-packages': 1.1.3 + ansi-colors: 4.1.3 + ci-info: 3.9.0 + enquirer: 2.4.1 + external-editor: 3.1.0 + fs-extra: 7.0.1 + mri: 1.2.0 + p-limit: 2.3.0 + package-manager-detector: 0.2.1 + picocolors: 1.1.0 + resolve-from: 5.0.0 + semver: 7.6.3 + spawndamnit: 2.0.0 + term-size: 2.2.1 + + '@changesets/config@3.0.3': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/logger': 0.1.1 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.8 + + '@changesets/errors@0.2.0': + dependencies: + extendable-error: 0.1.7 + + '@changesets/get-dependents-graph@2.1.2': + dependencies: + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + picocolors: 1.1.0 + semver: 7.6.3 + + '@changesets/get-github-info@0.6.0': + dependencies: + dataloader: 1.4.0 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + '@changesets/get-release-plan@4.0.4': + dependencies: + '@changesets/assemble-release-plan': 6.0.4 + '@changesets/config': 3.0.3 + '@changesets/pre': 2.0.1 + '@changesets/read': 0.6.1 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/get-version-range-type@0.4.0': {} + + '@changesets/git@3.0.1': + dependencies: + '@changesets/errors': 0.2.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.8 + spawndamnit: 2.0.0 + + '@changesets/logger@0.1.1': + dependencies: + picocolors: 1.1.0 + + '@changesets/parse@0.4.0': + dependencies: + '@changesets/types': 6.0.0 + js-yaml: 3.14.1 + + '@changesets/pre@2.0.1': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + + '@changesets/read@0.6.1': + dependencies: + '@changesets/git': 3.0.1 + '@changesets/logger': 0.1.1 + '@changesets/parse': 0.4.0 + '@changesets/types': 6.0.0 + fs-extra: 7.0.1 + p-filter: 2.1.0 + picocolors: 1.1.0 + + '@changesets/should-skip-package@0.1.1': + dependencies: + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/types@4.1.0': {} + + '@changesets/types@6.0.0': {} + + '@changesets/write@0.3.2': + dependencies: + '@changesets/types': 6.0.0 + fs-extra: 7.0.1 + human-id: 1.0.2 + prettier: 2.8.8 + + '@clockworklabs/test-app@file:packages/test-app': + dependencies: + '@clockworklabs/spacetimedb-sdk': link:packages/sdk + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@csstools/color-helpers@5.0.1': {} + + '@csstools/css-calc@2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-color-parser@3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/color-helpers': 5.0.1 + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-tokenizer@3.0.3': {} + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.23.1': + optional: true + + '@esbuild/aix-ppc64@0.24.2': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.23.1': + optional: true + + '@esbuild/android-arm64@0.24.2': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.23.1': + optional: true + + '@esbuild/android-arm@0.24.2': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.23.1': + optional: true + + '@esbuild/android-x64@0.24.2': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.23.1': + optional: true + + '@esbuild/darwin-arm64@0.24.2': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.23.1': + optional: true + + '@esbuild/darwin-x64@0.24.2': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.23.1': + optional: true + + '@esbuild/freebsd-arm64@0.24.2': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.23.1': + optional: true + + '@esbuild/freebsd-x64@0.24.2': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.23.1': + optional: true + + '@esbuild/linux-arm64@0.24.2': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.23.1': + optional: true + + '@esbuild/linux-arm@0.24.2': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.23.1': + optional: true + + '@esbuild/linux-ia32@0.24.2': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.23.1': + optional: true + + '@esbuild/linux-loong64@0.24.2': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.23.1': + optional: true + + '@esbuild/linux-mips64el@0.24.2': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.23.1': + optional: true + + '@esbuild/linux-ppc64@0.24.2': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.23.1': + optional: true + + '@esbuild/linux-riscv64@0.24.2': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.23.1': + optional: true + + '@esbuild/linux-s390x@0.24.2': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.23.1': + optional: true + + '@esbuild/linux-x64@0.24.2': + optional: true + + '@esbuild/netbsd-arm64@0.24.2': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/netbsd-x64@0.24.2': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.24.2': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-x64@0.24.2': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.23.1': + optional: true + + '@esbuild/sunos-x64@0.24.2': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.23.1': + optional: true + + '@esbuild/win32-arm64@0.24.2': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.23.1': + optional: true + + '@esbuild/win32-ia32@0.24.2': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.23.1': + optional: true + + '@esbuild/win32-x64@0.24.2': + optional: true + + '@eslint-community/eslint-utils@4.4.1(eslint@9.18.0)': + dependencies: + eslint: 9.18.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.19.1': + dependencies: + '@eslint/object-schema': 2.1.5 + debug: 4.3.7 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/core@0.10.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.2.0': + dependencies: + ajv: 6.12.6 + debug: 4.3.7 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.18.0': {} + + '@eslint/object-schema@2.1.5': {} + + '@eslint/plugin-kit@0.2.5': + dependencies: + '@eslint/core': 0.10.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.1': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 22.15.0 + '@types/yargs': 17.0.33 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/source-map@0.3.6': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@manypkg/find-root@1.1.0': + dependencies: + '@babel/runtime': 7.25.7 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + + '@manypkg/get-packages@1.1.3': + dependencies: + '@babel/runtime': 7.25.7 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rollup/rollup-android-arm-eabi@4.24.0': + optional: true + + '@rollup/rollup-android-arm64@4.24.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.24.0': + optional: true + + '@rollup/rollup-darwin-x64@4.24.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.24.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.24.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.24.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.24.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.24.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.24.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.24.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.24.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.24.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.24.0': + optional: true + + '@sinclair/typebox@0.27.8': {} + + '@testing-library/dom@10.4.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/runtime': 7.25.7 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.6.3': + dependencies: + '@adobe/css-tools': 4.4.1 + aria-query: 5.3.2 + chalk: 3.0.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + lodash: 4.17.21 + redent: 3.0.0 + + '@testing-library/react@16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.25.7 + '@testing-library/dom': 10.4.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.18 + '@types/react-dom': 18.3.5(@types/react@18.3.18) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.0)': + dependencies: + '@testing-library/dom': 10.4.0 + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.25.7 + '@babel/types': 7.25.7 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.25.7 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.25.7 + '@babel/types': 7.25.7 + + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.25.7 + + '@types/estree@1.0.6': {} + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@29.5.14': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + + '@types/json-schema@7.0.15': {} + + '@types/node@12.20.55': {} + + '@types/node@22.15.0': + dependencies: + undici-types: 6.21.0 + + '@types/prop-types@15.7.13': {} + + '@types/react-dom@18.3.0': + dependencies: + '@types/react': 18.3.11 + + '@types/react-dom@18.3.5(@types/react@18.3.18)': + dependencies: + '@types/react': 18.3.18 + + '@types/react@18.3.11': + dependencies: + '@types/prop-types': 15.7.13 + csstype: 3.1.3 + + '@types/react@18.3.18': + dependencies: + '@types/prop-types': 15.7.13 + csstype: 3.1.3 + + '@types/stack-utils@2.0.3': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.33': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/eslint-plugin@8.21.0(@typescript-eslint/parser@8.21.0(eslint@9.18.0)(typescript@5.6.2))(eslint@9.18.0)(typescript@5.6.2)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.21.0(eslint@9.18.0)(typescript@5.6.2) + '@typescript-eslint/scope-manager': 8.21.0 + '@typescript-eslint/type-utils': 8.21.0(eslint@9.18.0)(typescript@5.6.2) + '@typescript-eslint/utils': 8.21.0(eslint@9.18.0)(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 8.21.0 + eslint: 9.18.0 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 2.0.0(typescript@5.6.2) + typescript: 5.6.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.21.0(eslint@9.18.0)(typescript@5.6.2)': + dependencies: + '@typescript-eslint/scope-manager': 8.21.0 + '@typescript-eslint/types': 8.21.0 + '@typescript-eslint/typescript-estree': 8.21.0(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 8.21.0 + debug: 4.3.7 + eslint: 9.18.0 + typescript: 5.6.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.21.0': + dependencies: + '@typescript-eslint/types': 8.21.0 + '@typescript-eslint/visitor-keys': 8.21.0 + + '@typescript-eslint/type-utils@8.21.0(eslint@9.18.0)(typescript@5.6.2)': + dependencies: + '@typescript-eslint/typescript-estree': 8.21.0(typescript@5.6.2) + '@typescript-eslint/utils': 8.21.0(eslint@9.18.0)(typescript@5.6.2) + debug: 4.3.7 + eslint: 9.18.0 + ts-api-utils: 2.0.0(typescript@5.6.2) + typescript: 5.6.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.21.0': {} + + '@typescript-eslint/typescript-estree@8.21.0(typescript@5.6.2)': + dependencies: + '@typescript-eslint/types': 8.21.0 + '@typescript-eslint/visitor-keys': 8.21.0 + debug: 4.3.7 + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 2.0.0(typescript@5.6.2) + typescript: 5.6.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.21.0(eslint@9.18.0)(typescript@5.6.2)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.18.0) + '@typescript-eslint/scope-manager': 8.21.0 + '@typescript-eslint/types': 8.21.0 + '@typescript-eslint/typescript-estree': 8.21.0(typescript@5.6.2) + eslint: 9.18.0 + typescript: 5.6.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.21.0': + dependencies: + '@typescript-eslint/types': 8.21.0 + eslint-visitor-keys: 4.2.0 + + '@vitejs/plugin-react@4.3.2(vite@5.4.8(@types/node@22.15.0)(terser@5.34.1))': + dependencies: + '@babel/core': 7.25.7 + '@babel/plugin-transform-react-jsx-self': 7.25.7(@babel/core@7.25.7) + '@babel/plugin-transform-react-jsx-source': 7.25.7(@babel/core@7.25.7) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 5.4.8(@types/node@22.15.0)(terser@5.34.1) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-react@4.3.4(vite@6.0.11(@types/node@22.15.0)(terser@5.34.1)(tsx@4.19.1))': + dependencies: + '@babel/core': 7.26.0 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 6.0.11(@types/node@22.15.0)(terser@5.34.1)(tsx@4.19.1) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@2.1.2': + dependencies: + '@vitest/spy': 2.1.2 + '@vitest/utils': 2.1.2 + chai: 5.1.1 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.2(@vitest/spy@2.1.2)(vite@5.4.8(@types/node@22.15.0)(terser@5.34.1))': + dependencies: + '@vitest/spy': 2.1.2 + estree-walker: 3.0.3 + magic-string: 0.30.11 + optionalDependencies: + vite: 5.4.8(@types/node@22.15.0)(terser@5.34.1) + + '@vitest/pretty-format@2.1.2': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.2': + dependencies: + '@vitest/utils': 2.1.2 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.2': + dependencies: + '@vitest/pretty-format': 2.1.2 + magic-string: 0.30.11 + pathe: 1.1.2 + + '@vitest/spy@2.1.2': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.2': + dependencies: + '@vitest/pretty-format': 2.1.2 + loupe: 3.1.2 + tinyrainbow: 1.2.0 + + acorn-jsx@5.3.2(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.14.0 + + acorn@8.12.1: {} + + acorn@8.14.0: {} + + agent-base@7.1.3: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@4.1.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + array-union@2.1.0: {} + + assertion-error@2.0.1: {} + + asynckit@0.4.0: {} + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + better-path-resolve@1.0.0: + dependencies: + is-windows: 1.0.2 + + binary-extensions@2.3.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + brotli-size-cli@1.0.0: + dependencies: + brotli-size: 4.0.0 + pretty-bytes: 5.6.0 + sade: 1.8.1 + + brotli-size@4.0.0: + dependencies: + duplexer: 0.1.1 + + browserslist@4.24.0: + dependencies: + caniuse-lite: 1.0.30001667 + electron-to-chromium: 1.5.33 + node-releases: 2.0.18 + update-browserslist-db: 1.1.1(browserslist@4.24.0) + + buffer-from@1.1.2: {} + + bundle-require@5.0.0(esbuild@0.23.1): + dependencies: + esbuild: 0.23.1 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001667: {} + + chai@5.1.1: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.2 + pathval: 2.0.0 + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@3.0.0: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chardet@0.7.0: {} + + check-error@2.1.1: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + ci-info@3.9.0: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@2.20.3: {} + + commander@4.1.1: {} + + concat-map@0.0.1: {} + + consola@3.2.3: {} + + convert-source-map@2.0.0: {} + + create-require@1.1.1: {} + + cross-spawn@5.1.0: + dependencies: + lru-cache: 4.1.5 + shebang-command: 1.2.0 + which: 1.3.1 + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css.escape@1.5.1: {} + + cssstyle@4.2.1: + dependencies: + '@asamuzakjp/css-color': 2.8.3 + rrweb-cssom: 0.8.0 + + csstype@3.1.3: {} + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.1.0 + + dataloader@1.4.0: {} + + debug@4.3.7: + dependencies: + ms: 2.1.3 + + decimal.js@10.5.0: {} + + deep-eql@5.0.2: {} + + deep-is@0.1.4: {} + + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + + detect-indent@6.1.0: {} + + diff-sequences@29.6.3: {} + + diff@4.0.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dotenv@8.6.0: {} + + duplexer@0.1.1: {} + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.33: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + + entities@4.5.0: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + + esbuild@0.24.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 + + escalade@3.2.0: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@5.1.0(eslint@9.18.0): + dependencies: + eslint: 9.18.0 + + eslint-plugin-react-refresh@0.4.18(eslint@9.18.0): + dependencies: + eslint: 9.18.0 + + eslint-scope@8.2.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.18.0: + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.18.0) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.19.1 + '@eslint/core': 0.10.0 + '@eslint/eslintrc': 3.2.0 + '@eslint/js': 9.18.0 + '@eslint/plugin-kit': 0.2.5 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.1 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.3.7 + escape-string-regexp: 4.0.0 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 + + esprima@4.0.1: {} + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.6 + + esutils@2.0.3: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + extendable-error@0.1.7: {} + + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fdir@6.4.0(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.2 + keyv: 4.5.4 + + flatted@3.3.2: {} + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + + form-data@4.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-stream@6.0.1: {} + + get-tsconfig@4.8.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + globals@11.12.0: {} + + globals@14.0.0: {} + + globals@15.14.0: {} + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.3 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + + human-id@1.0.2: {} + + human-signals@2.1.0: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + is-stream@2.0.1: {} + + is-subdir@1.2.0: + dependencies: + better-path-resolve: 1.0.0 + + is-windows@1.0.2: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.26.2 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.15.0 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + joycon@3.1.1: {} + + js-tokens@4.0.0: {} + + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsdom@26.0.0: + dependencies: + cssstyle: 4.2.1 + data-urls: 5.0.0 + decimal.js: 10.5.0 + form-data: 4.0.1 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.16 + parse5: 7.2.1 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.1.0 + ws: 8.18.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsesc@3.0.2: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.2: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lodash.sortby@4.7.0: {} + + lodash.startcase@4.4.0: {} + + lodash@4.17.21: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + loupe@3.1.2: {} + + lru-cache@10.4.3: {} + + lru-cache@4.1.5: + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lz-string@1.5.0: {} + + magic-string@0.30.11: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + make-error@1.3.6: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@2.1.0: {} + + min-indent@1.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minipass@7.1.2: {} + + mri@1.2.0: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.7: {} + + nanoid@3.3.8: {} + + natural-compare@1.4.0: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-releases@2.0.18: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nwsapi@2.2.16: {} + + object-assign@4.1.1: {} + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + os-tmpdir@1.0.2: {} + + outdent@0.5.0: {} + + p-filter@2.1.0: + dependencies: + p-map: 2.1.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@2.1.0: {} + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + package-manager-detector@0.2.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse5@7.2.1: + dependencies: + entities: 4.5.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-type@4.0.0: {} + + pathe@1.1.2: {} + + pathval@2.0.0: {} + + picocolors@1.1.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.2: {} + + pify@4.0.1: {} + + pirates@4.0.6: {} + + postcss-load-config@6.0.1(postcss@8.5.1)(tsx@4.19.1): + dependencies: + lilconfig: 3.1.2 + optionalDependencies: + postcss: 8.5.1 + tsx: 4.19.1 + + postcss@8.4.47: + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.0 + source-map-js: 1.2.1 + + postcss@8.5.1: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier@2.8.8: {} + + prettier@3.3.3: {} + + pretty-bytes@5.6.0: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + pseudomap@1.0.2: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-is@17.0.2: {} + + react-is@18.3.1: {} + + react-refresh@0.14.2: {} + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-yaml-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + regenerator-runtime@0.14.1: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + reusify@1.0.4: {} + + rollup@4.24.0: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.24.0 + '@rollup/rollup-android-arm64': 4.24.0 + '@rollup/rollup-darwin-arm64': 4.24.0 + '@rollup/rollup-darwin-x64': 4.24.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.24.0 + '@rollup/rollup-linux-arm-musleabihf': 4.24.0 + '@rollup/rollup-linux-arm64-gnu': 4.24.0 + '@rollup/rollup-linux-arm64-musl': 4.24.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0 + '@rollup/rollup-linux-riscv64-gnu': 4.24.0 + '@rollup/rollup-linux-s390x-gnu': 4.24.0 + '@rollup/rollup-linux-x64-gnu': 4.24.0 + '@rollup/rollup-linux-x64-musl': 4.24.0 + '@rollup/rollup-win32-arm64-msvc': 4.24.0 + '@rollup/rollup-win32-ia32-msvc': 4.24.0 + '@rollup/rollup-win32-x64-msvc': 4.24.0 + fsevents: 2.3.3 + + rrweb-cssom@0.8.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + sade@1.8.1: + dependencies: + mri: 1.2.0 + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@6.3.1: {} + + semver@7.6.3: {} + + shebang-command@1.2.0: + dependencies: + shebang-regex: 1.0.0 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@1.0.0: {} + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + + spawndamnit@2.0.0: + dependencies: + cross-spawn: 5.1.0 + signal-exit: 3.0.7 + + sprintf-js@1.0.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + stackback@0.0.2: {} + + std-env@3.7.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-bom@3.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + symbol-tree@3.2.4: {} + + term-size@2.2.1: {} + + terser@5.34.1: + dependencies: + '@jridgewell/source-map': 0.3.6 + acorn: 8.12.1 + commander: 2.20.3 + source-map-support: 0.5.21 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.0: {} + + tinyglobby@0.2.9: + dependencies: + fdir: 6.4.0(picomatch@4.0.2) + picomatch: 4.0.2 + + tinypool@1.0.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + tldts-core@6.1.75: {} + + tldts@6.1.75: + dependencies: + tldts-core: 6.1.75 + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + to-fast-properties@2.0.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tough-cookie@5.1.0: + dependencies: + tldts: 6.1.75 + + tr46@0.0.3: {} + + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + + tr46@5.0.0: + dependencies: + punycode: 2.3.1 + + tree-kill@1.2.2: {} + + ts-api-utils@2.0.0(typescript@5.6.2): + dependencies: + typescript: 5.6.2 + + ts-interface-checker@0.1.13: {} + + ts-node@10.9.2(@types/node@22.15.0)(typescript@5.8.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 22.15.0 + acorn: 8.14.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.8.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tsup@8.3.0(postcss@8.5.1)(tsx@4.19.1)(typescript@5.6.2): + dependencies: + bundle-require: 5.0.0(esbuild@0.23.1) + cac: 6.7.14 + chokidar: 3.6.0 + consola: 3.2.3 + debug: 4.3.7 + esbuild: 0.23.1 + execa: 5.1.1 + joycon: 3.1.1 + picocolors: 1.1.0 + postcss-load-config: 6.0.1(postcss@8.5.1)(tsx@4.19.1) + resolve-from: 5.0.0 + rollup: 4.24.0 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tinyglobby: 0.2.9 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.1 + typescript: 5.6.2 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + tsup@8.3.0(postcss@8.5.1)(tsx@4.19.1)(typescript@5.8.3): + dependencies: + bundle-require: 5.0.0(esbuild@0.23.1) + cac: 6.7.14 + chokidar: 3.6.0 + consola: 3.2.3 + debug: 4.3.7 + esbuild: 0.23.1 + execa: 5.1.1 + joycon: 3.1.1 + picocolors: 1.1.0 + postcss-load-config: 6.0.1(postcss@8.5.1)(tsx@4.19.1) + resolve-from: 5.0.0 + rollup: 4.24.0 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tinyglobby: 0.2.9 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.1 + typescript: 5.8.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + tsx@4.19.1: + dependencies: + esbuild: 0.23.1 + get-tsconfig: 4.8.1 + optionalDependencies: + fsevents: 2.3.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.21.0(eslint@9.18.0)(typescript@5.6.2): + dependencies: + '@typescript-eslint/eslint-plugin': 8.21.0(@typescript-eslint/parser@8.21.0(eslint@9.18.0)(typescript@5.6.2))(eslint@9.18.0)(typescript@5.6.2) + '@typescript-eslint/parser': 8.21.0(eslint@9.18.0)(typescript@5.6.2) + '@typescript-eslint/utils': 8.21.0(eslint@9.18.0)(typescript@5.6.2) + eslint: 9.18.0 + typescript: 5.6.2 + transitivePeerDependencies: + - supports-color + + typescript@5.6.2: {} + + typescript@5.8.3: {} + + undici-types@6.21.0: {} + + undici@6.19.8: {} + + universalify@0.1.2: {} + + update-browserslist-db@1.1.1(browserslist@4.24.0): + dependencies: + browserslist: 4.24.0 + escalade: 3.2.0 + picocolors: 1.1.0 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + v8-compile-cache-lib@3.0.1: {} + + vite-node@2.1.2(@types/node@22.15.0)(terser@5.34.1): + dependencies: + cac: 6.7.14 + debug: 4.3.7 + pathe: 1.1.2 + vite: 5.4.8(@types/node@22.15.0)(terser@5.34.1) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.8(@types/node@22.15.0)(terser@5.34.1): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.47 + rollup: 4.24.0 + optionalDependencies: + '@types/node': 22.15.0 + fsevents: 2.3.3 + terser: 5.34.1 + + vite@6.0.11(@types/node@22.15.0)(terser@5.34.1)(tsx@4.19.1): + dependencies: + esbuild: 0.24.2 + postcss: 8.5.1 + rollup: 4.24.0 + optionalDependencies: + '@types/node': 22.15.0 + fsevents: 2.3.3 + terser: 5.34.1 + tsx: 4.19.1 + + vitest@2.1.2(@types/node@22.15.0)(jsdom@26.0.0)(terser@5.34.1): + dependencies: + '@vitest/expect': 2.1.2 + '@vitest/mocker': 2.1.2(@vitest/spy@2.1.2)(vite@5.4.8(@types/node@22.15.0)(terser@5.34.1)) + '@vitest/pretty-format': 2.1.2 + '@vitest/runner': 2.1.2 + '@vitest/snapshot': 2.1.2 + '@vitest/spy': 2.1.2 + '@vitest/utils': 2.1.2 + chai: 5.1.1 + debug: 4.3.7 + magic-string: 0.30.11 + pathe: 1.1.2 + std-env: 3.7.0 + tinybench: 2.9.0 + tinyexec: 0.3.0 + tinypool: 1.0.1 + tinyrainbow: 1.2.0 + vite: 5.4.8(@types/node@22.15.0)(terser@5.34.1) + vite-node: 2.1.2(@types/node@22.15.0)(terser@5.34.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.15.0 + jsdom: 26.0.0 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@3.0.1: {} + + webidl-conversions@4.0.2: {} + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.1.0: + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + + which@1.3.1: + dependencies: + isexe: 2.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + ws@8.18.0: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + yallist@2.1.2: {} + + yallist@3.1.1: {} + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} diff --git a/sdks/typescript/pnpm-workspace.yaml b/sdks/typescript/pnpm-workspace.yaml new file mode 100644 index 00000000000..02495561277 --- /dev/null +++ b/sdks/typescript/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - 'packages/*' + - 'examples/**' diff --git a/sdks/typescript/tsup.config.ts b/sdks/typescript/tsup.config.ts new file mode 100644 index 00000000000..779fe900a4e --- /dev/null +++ b/sdks/typescript/tsup.config.ts @@ -0,0 +1,56 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig([ + { + entryPoints: { + index: 'src/index.ts', + }, + format: ['esm'], + target: 'es2022', + legacyOutput: false, + dts: { + resolve: true, + }, + clean: true, + platform: 'browser', + noExternal: ['brotli', 'buffer'], + treeshake: 'smallest', + external: ['undici'], + env: { + BROWSER: 'false', + }, + }, + { + entryPoints: { + index: 'src/index.ts', + }, + format: ['esm'], + target: 'es2022', + legacyOutput: false, + dts: false, + outDir: 'dist/browser', + clean: true, + platform: 'browser', + noExternal: ['brotli', 'buffer'], + treeshake: 'smallest', + external: ['undici'], + env: { + BROWSER: 'true', + }, + }, + { + entryPoints: { + index: 'src/index.ts', + }, + format: ['esm'], + target: 'es2022', + outDir: 'dist/min', + dts: false, + sourcemap: true, + noExternal: ['brotli', 'buffer', 'events'], + treeshake: 'smallest', + minify: 'terser', + platform: 'browser', + external: ['undici'], + }, +]); diff --git a/sdks/typescript/vitest.config.ts b/sdks/typescript/vitest.config.ts new file mode 100644 index 00000000000..4ac6027d578 --- /dev/null +++ b/sdks/typescript/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + }, +});